diff --git a/build.sbt b/build.sbt index b565316..8e3304d 100644 --- a/build.sbt +++ b/build.sbt @@ -2,8 +2,8 @@ import java.io.{BufferedReader, InputStreamReader} enablePlugins(JavaAppPackaging) -lazy val xmlCalabashVersion = "2.99.8" -lazy val jafplVersion = "0.3.75" +lazy val xmlCalabashVersion = "2.99.9" +lazy val jafplVersion = "0.3.83" lazy val saxonVersion = "10.6" lazy val useSaxonEE = Option(System.getProperty("saxonEdition")).getOrElse("HE") == "EE" diff --git a/src/main/resources/com.xmlcalabash.library.xpl b/src/main/resources/com.xmlcalabash.library.xpl index 60dd8db..9a3abe3 100644 --- a/src/main/resources/com.xmlcalabash.library.xpl +++ b/src/main/resources/com.xmlcalabash.library.xpl @@ -3,10 +3,6 @@ xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0"> - - - - @@ -152,7 +148,7 @@ - + @@ -178,7 +174,7 @@ - @@ -191,7 +187,7 @@ - @@ -209,7 +205,7 @@ - + @@ -222,9 +218,9 @@ - - - + + @@ -328,9 +324,9 @@ - - - + + + @@ -339,7 +335,7 @@ - + @@ -429,8 +425,8 @@ - - + + @@ -445,7 +441,7 @@ - + @@ -455,7 +451,7 @@ - + @@ -536,11 +532,19 @@ + + + + + + + + @@ -549,4 +553,9 @@ + + + + + diff --git a/src/main/resources/com.xmlcalabash.properties b/src/main/resources/com.xmlcalabash.properties deleted file mode 100644 index a29f2a2..0000000 --- a/src/main/resources/com.xmlcalabash.properties +++ /dev/null @@ -1,93 +0,0 @@ -cx = namespace http://xmlcalabash.com/ns/extensions -p = namespace http://www.w3.org/ns/xproc -exf = namespace http://exproc.org/standard/functions - -com.xmlcalabash.functions.Cwd = function exf:cwd -com.xmlcalabash.functions.DocumentProperties = function p:document-properties -com.xmlcalabash.functions.DocumentProperty = function p:document-property -com.xmlcalabash.functions.ForceQNameKeys = function p:force-qname-keys -com.xmlcalabash.functions.InjElapsed = function cx:step-elapsed -com.xmlcalabash.functions.InjId = function cx:injectable-id -com.xmlcalabash.functions.InjName = function cx:step-name -com.xmlcalabash.functions.InjType = function cx:step-type -com.xmlcalabash.functions.SystemProperty = function p:system-property -com.xmlcalabash.functions.StepAvailable = function p:step-available -com.xmlcalabash.functions.IterationPosition = function p:iteration-position -com.xmlcalabash.functions.IterationSize = function p:iteration-size -com.xmlcalabash.functions.UrifyFunction = function p:urify - -com.xmlcalabash.steps.AddAttribute = step p:add-attribute -com.xmlcalabash.steps.Archive = step p:archive -com.xmlcalabash.steps.ArchiveManifest = step p:archive-manifest -com.xmlcalabash.steps.CastContentType = step p:cast-content-type -com.xmlcalabash.steps.Count = step p:count -com.xmlcalabash.steps.Compress = step p:compress -com.xmlcalabash.steps.Delete = step p:delete -com.xmlcalabash.steps.Error = step p:error -com.xmlcalabash.steps.EscapeMarkup = step p:escape-markup -com.xmlcalabash.steps.file.DirectoryList = step p:directory-list -com.xmlcalabash.steps.file.FileCopy = step p:file-copy -com.xmlcalabash.steps.file.FileDelete = step p:file-delete -com.xmlcalabash.steps.file.FileInfo = step p:file-info -com.xmlcalabash.steps.file.FileMkdir = step p:file-mkdir -com.xmlcalabash.steps.file.FileMove = step p:file-move -com.xmlcalabash.steps.file.FileCreateTempFile = step p:file-create-tempfile -com.xmlcalabash.steps.file.FileTouch = step p:file-touch -com.xmlcalabash.steps.Filter = step p:filter -com.xmlcalabash.steps.Hash = step p:hash -com.xmlcalabash.steps.HttpRequest = step p:http-request -com.xmlcalabash.steps.Identity = step p:identity -com.xmlcalabash.steps.Insert = step p:insert -com.xmlcalabash.steps.json.Join = step p:json-join -com.xmlcalabash.steps.json.Merge = step p:json-merge -com.xmlcalabash.steps.LabelElements = step p:label-elements -com.xmlcalabash.steps.Load = step p:load -com.xmlcalabash.steps.NamespaceDelete = step p:namespace-delete -com.xmlcalabash.steps.NamespaceRename = step p:namespace-rename -com.xmlcalabash.steps.os.OsInfo = step p:os-info -com.xmlcalabash.steps.os.OsExec = step p:os-exec -com.xmlcalabash.steps.Pack = step p:pack -com.xmlcalabash.steps.Parameters = step p:parameters -com.xmlcalabash.steps.Rename = step p:rename -com.xmlcalabash.steps.Replace = step p:replace -com.xmlcalabash.steps.SetAttributes = step p:set-attributes -com.xmlcalabash.steps.SetProperties = step p:set-properties -com.xmlcalabash.steps.Sink = step p:sink -com.xmlcalabash.steps.SplitSequence = step p:split-sequence -com.xmlcalabash.steps.Store = step p:store -com.xmlcalabash.steps.StringReplace = step p:string-replace -com.xmlcalabash.steps.text.Count = step p:text-count -com.xmlcalabash.steps.text.Head = step p:text-head -com.xmlcalabash.steps.text.Join = step p:text-join -com.xmlcalabash.steps.text.Replace = step p:text-replace -com.xmlcalabash.steps.text.Sort = step p:text-sort -com.xmlcalabash.steps.text.Tail = step p:text-tail -com.xmlcalabash.steps.Unarchive = step p:unarchive -com.xmlcalabash.steps.Uncompress = step p:uncompress -com.xmlcalabash.steps.UnescapeMarkup = step p:unescape-markup -com.xmlcalabash.steps.Unwrap = step p:unwrap -com.xmlcalabash.steps.WwwFormUrlDecode = step p:www-form-urldecode -com.xmlcalabash.steps.WwwFormUrlEncode = step p:www-form-urlencode -com.xmlcalabash.steps.Uuid = step p:uuid -com.xmlcalabash.steps.ValidateWithRNG = step p:validate-with-relax-ng -com.xmlcalabash.steps.ValidateWithSCH = step p:validate-with-schematron -com.xmlcalabash.steps.ValidateWithXSD = step p:validate-with-xml-schema -com.xmlcalabash.steps.Wrap = step p:wrap -com.xmlcalabash.steps.WrapSequence = step p:wrap-sequence -com.xmlcalabash.steps.XInclude = step p:xinclude -com.xmlcalabash.steps.XQuery = step p:xquery -com.xmlcalabash.steps.Xslt = step p:xslt -com.xmlcalabash.steps.B64Decode = step cx:base64-decode -com.xmlcalabash.steps.B64Encode = step cx:base64-encode -com.xmlcalabash.steps.ExceptionTranslator = step cx:exception-translator -com.xmlcalabash.steps.JavaScript = step cx:javascript -com.xmlcalabash.steps.Markdown = step cx:markdown -com.xmlcalabash.steps.OptionValue = step cx:option-value -com.xmlcalabash.steps.PropertyExtract = step cx:property-extract -com.xmlcalabash.steps.PropertyMerge = step cx:property-merge -com.xmlcalabash.steps.internal.ContentTypeChecker = step cx:content-type-checker -com.xmlcalabash.steps.internal.SelectFilter = step cx:select-filter -com.xmlcalabash.steps.internal.DocumentLoader = step cx:document-loader -com.xmlcalabash.steps.internal.InlineLoader = step cx:inline-loader -com.xmlcalabash.steps.internal.EmptyLoader = step cx:empty-loader - diff --git a/src/main/resources/com.xmlcalabash.settings b/src/main/resources/com.xmlcalabash.settings new file mode 100644 index 0000000..ba8ee4d --- /dev/null +++ b/src/main/resources/com.xmlcalabash.settings @@ -0,0 +1,95 @@ +namespace http://xmlcalabash.com/ns/extensions = cx +namespace http://www.w3.org/ns/xproc = p +namespace http://exproc.org/standard/functions = exf + +function exf:cwd = com.xmlcalabash.functions.Cwd +function p:document-properties = com.xmlcalabash.functions.DocumentProperties +function p:document-property = com.xmlcalabash.functions.DocumentProperty +function p:force-qname-keys = com.xmlcalabash.functions.ForceQNameKeys +function cx:step-elapsed = com.xmlcalabash.functions.InjElapsed +function cx:injectable-id = com.xmlcalabash.functions.InjId +function cx:step-name = com.xmlcalabash.functions.InjName +function cx:step-type = com.xmlcalabash.functions.InjType +function p:system-property = com.xmlcalabash.functions.SystemProperty +function p:step-available = com.xmlcalabash.functions.StepAvailable +function p:iteration-position = com.xmlcalabash.functions.IterationPosition +function p:iteration-size = com.xmlcalabash.functions.IterationSize +function p:urify = com.xmlcalabash.functions.UrifyFunction + +step p:add-attribute = com.xmlcalabash.steps.AddAttribute +step p:archive = com.xmlcalabash.steps.Archive +step p:archive-manifest = com.xmlcalabash.steps.ArchiveManifest +step p:cast-content-type = com.xmlcalabash.steps.CastContentType +step p:count = com.xmlcalabash.steps.Count +step p:compress = com.xmlcalabash.steps.Compress +step p:delete = com.xmlcalabash.steps.Delete +step p:error = com.xmlcalabash.steps.Error +step p:escape-markup = com.xmlcalabash.steps.EscapeMarkup +step p:directory-list = com.xmlcalabash.steps.file.DirectoryList +step p:file-copy = com.xmlcalabash.steps.file.FileCopy +step p:file-delete = com.xmlcalabash.steps.file.FileDelete +step p:file-info = com.xmlcalabash.steps.file.FileInfo +step p:file-mkdir = com.xmlcalabash.steps.file.FileMkdir +step p:file-move = com.xmlcalabash.steps.file.FileMove +step p:file-create-tempfile = com.xmlcalabash.steps.file.FileCreateTempFile +step p:file-touch = com.xmlcalabash.steps.file.FileTouch +step p:filter = com.xmlcalabash.steps.Filter +step p:hash = com.xmlcalabash.steps.Hash +step p:http-request = com.xmlcalabash.steps.HttpRequest +step p:identity = com.xmlcalabash.steps.Identity +step p:insert = com.xmlcalabash.steps.Insert +step p:json-join = com.xmlcalabash.steps.json.Join +step p:json-merge = com.xmlcalabash.steps.json.Merge +step p:label-elements = com.xmlcalabash.steps.LabelElements +step p:load = com.xmlcalabash.steps.Load +step p:namespace-delete = com.xmlcalabash.steps.NamespaceDelete +step p:namespace-rename = com.xmlcalabash.steps.NamespaceRename +step p:os-info = com.xmlcalabash.steps.os.OsInfo +step p:os-exec = com.xmlcalabash.steps.os.OsExec +step p:pack = com.xmlcalabash.steps.Pack +step p:parameters = com.xmlcalabash.steps.Parameters +step p:rename = com.xmlcalabash.steps.Rename +step p:replace = com.xmlcalabash.steps.Replace +step p:set-attributes = com.xmlcalabash.steps.SetAttributes +step p:set-properties = com.xmlcalabash.steps.SetProperties +step p:sink = com.xmlcalabash.steps.Sink +step p:split-sequence = com.xmlcalabash.steps.SplitSequence +step p:store = com.xmlcalabash.steps.Store +step p:string-replace = com.xmlcalabash.steps.StringReplace +step p:text-count = com.xmlcalabash.steps.text.Count +step p:text-head = com.xmlcalabash.steps.text.Head +step p:text-join = com.xmlcalabash.steps.text.Join +step p:text-replace = com.xmlcalabash.steps.text.Replace +step p:text-sort = com.xmlcalabash.steps.text.Sort +step p:text-tail = com.xmlcalabash.steps.text.Tail +step p:unarchive = com.xmlcalabash.steps.Unarchive +step p:uncompress = com.xmlcalabash.steps.Uncompress +step p:unescape-markup = com.xmlcalabash.steps.UnescapeMarkup +step p:unwrap = com.xmlcalabash.steps.Unwrap +step p:www-form-urldecode = com.xmlcalabash.steps.WwwFormUrlDecode +step p:www-form-urlencode = com.xmlcalabash.steps.WwwFormUrlEncode +step p:uuid = com.xmlcalabash.steps.Uuid +step p:validate-with-relax-ng = com.xmlcalabash.steps.ValidateWithRNG +step p:validate-with-schematron = com.xmlcalabash.steps.ValidateWithSCH +step p:validate-with-xml-schema = com.xmlcalabash.steps.ValidateWithXSD +step p:wrap = com.xmlcalabash.steps.Wrap +step p:wrap-sequence = com.xmlcalabash.steps.WrapSequence +step p:xinclude = com.xmlcalabash.steps.XInclude +step p:xquery = com.xmlcalabash.steps.XQuery +step p:xslt = com.xmlcalabash.steps.Xslt +step cx:base64-decode = com.xmlcalabash.steps.B64Decode +step cx:base64-encode = com.xmlcalabash.steps.B64Encode +step cx:exception-translator = com.xmlcalabash.steps.ExceptionTranslator +step cx:javascript = com.xmlcalabash.steps.JavaScript +step cx:markdown = com.xmlcalabash.steps.Markdown +step cx:option-value = com.xmlcalabash.steps.OptionValue +step cx:property-extract = com.xmlcalabash.steps.PropertyExtract +step cx:property-merge = com.xmlcalabash.steps.PropertyMerge +step cx:content-type-checker = com.xmlcalabash.steps.internal.ContentTypeChecker +step cx:select-filter = com.xmlcalabash.steps.internal.SelectFilter +step cx:document-loader = com.xmlcalabash.steps.internal.DocumentLoader +step cx:document-loader-vt = com.xmlcalabash.steps.internal.DocumentLoader +step cx:inline-loader = com.xmlcalabash.steps.internal.InlineLoader +step cx:inline-loader-vt = com.xmlcalabash.steps.internal.InlineLoader +step cx:empty-loader = com.xmlcalabash.steps.internal.EmptyLoader +step cx:value-computation = com.xmlcalabash.steps.internal.ValueComputation diff --git a/src/main/resources/com/xmlcalabash/stylesheets/pl2dot.xsl b/src/main/resources/com/xmlcalabash/stylesheets/pl2dot.xsl index 60d4298..c48fca7 100644 --- a/src/main/resources/com/xmlcalabash/stylesheets/pl2dot.xsl +++ b/src/main/resources/com/xmlcalabash/stylesheets/pl2dot.xsl @@ -14,7 +14,8 @@ - + @@ -52,33 +53,51 @@ - + select="'$' || @name || ' ' || local-name(.)"/> + + - - - + + + + + + + + + + + + + + + + + + + select="concat(if (starts-with(@name, '!syn')) + then '' else concat(@name, ' '), + $step-label)"/> @@ -102,7 +121,7 @@ - @@ -111,11 +130,11 @@ - - diff --git a/src/main/resources/explain-errors.txt b/src/main/resources/explain-errors.txt index 48819e7..6126988 100644 --- a/src/main/resources/explain-errors.txt +++ b/src/main/resources/explain-errors.txt @@ -62,16 +62,19 @@ Validity error in “$1”: $2 XD0026 The context item is empty in expression: “$1” (Saxon: $2) +XD0028 +Value does not satisfy type: “$1” + XD0030 Error: $1 XD0034 $1 -XD0036 +XD0036/1 Computed result “$1” does not match specified type “$2”. -XD0036 +XD0036/2 Computed result “$2” does not match specified type “$3” for “$1”. XD0038 @@ -182,6 +185,9 @@ There is no port named “$2” on “$1” steps. XS0011 Duplicate port name: ‘$1’ +XS0014 +Attempt to make output port ‘$1’ primary when ‘$2’ is already primary. + XS0017 Option is both required and has default value: $1. @@ -200,11 +206,17 @@ There’s no primary output port on the step named “$1”. XS0022/4 The port “$2” on “$1” is not readable from here. +XS0025/1 +Step types must explicitly be in a namespace. + +XS0025/2 +Step types may not be declared in the “$1” namespace. + XS0028 Name cannot be in the XProc namespace: “$1”. XS0030 -Attempt to make port ‘$1’ primary when ‘$2’ is already primary. +Attempt to make input port ‘$1’ primary when ‘$2’ is already primary. XS0031 The option named ‘$2’ is not allowed on a ‘$1’ step. @@ -236,6 +248,12 @@ Error p:import cannot import a “$1”. XS0053 An imported pipeline must have a type. +XS0059/1 +The pipeline document has no root element. + +XS0059/2 +The pipeline document has an invalid root element: “$1”. + XS0064/1 Only the last p:catch may omit the code. @@ -276,20 +294,23 @@ Invalid output port: “$1”. The outputs of p:finally must be different from t XS0073 There is no in-scope step named “$1”. +XS0074 +A p:choose must have at least one p:when or p:otherwise. + XS0075 Invalid p:try: $1. -XS0077 +XS0077/1 Invalid value specified: “$1”. Value must be $2. -XS0079/1 -Comment not allowed here. Found “$1”. +XS0077/2 +Invalid value, an empty string is not allowed here. -XS0079/2 -Processing instruction not allowed here. Found “$1”. +XS0077/3 +Invalid value “$1”, visibility must be “public” or “private”. -XS0079/3 -Text not allowed here. Found “$1”. +XS0079/1 +Can’t mix comments, processing-instructions, or (non-ws) text here. XS0080 Duplicated option name: “$1”. @@ -312,27 +333,51 @@ More than one p:with-input for the same port: “$1” XS0087 There’s no in-scope namespace binding for the name “$1”. +XS0088 +“$1” shadows static option. + XS0089 If a p:empty binding is used, it must be the only binding. XS0090 Invalid pipe attribute value: “$1”; must be port, port@step, or @step. +XS0092 +The value of a static option cannot be changed: “$1”. + +XS0095 +An option may not be both required and static: “$1”. + XS0096 Invalid sequence type: “$1”. $2. +XS0097 +An attribute in the XProc namespace (“$1”) is not allowed on an element in the XProc namespace: “$2” + XS0100/1 Invalid pipeline: $1. XS0100/2 XProc element not allowed here: “$1” +XS0100/3 +Cannot mix implicit inlines with other connections: “$1”. + XS0102/1 -Mismatched primary outputs in p:choose, “$1” != “$2” +Mismatched primary outputs in p:choose, “$1” != unnamed port XS0102/2 +Mismatched primary outputs in p:choose, “$1” != “$2” + +XS0102/3 +Mismatched primary outputs in p:try, “$1” != unnamed + +XS0102/4 Mismatched primary outputs in p:try, “$1” != “$2” +XS0103/1 +Mismatched primary outputs in p:choose, “$1” != “$2” + XS0107/1 There is no variable or reference named “$1” in scope. @@ -345,12 +390,33 @@ Static expression references context item. XS0107/4 Static expression references non-static variable “$1” +XS0107/5 +Output port “$1” must not have bindings on atomic step type “$2”. + +XS0107/6 +Output port “$1” must not have bindings on atomic step. + XS0108 It is a static error if a p:if does not specify a primary output port. +XS0111 +Unrecognized content type shortcut value: “$1”. + XS0112 No output on p:finally may be primary: “$1” +XS0113 +Value of the $1 attribute must be ‘true’ or ‘false’. + +XS0114 +The port “$1” is not allowed on “$2” steps. + +XS0115/1 +Unresolvable deadlock in use-when: “$1”. + +XS0115/2 +Unresolvable deadlock in use-when: “$1”. + XC0001 The content type is not a valid text content type: “$1”. @@ -528,6 +594,9 @@ Delete not allowed: $1 XC0151 Schema is not a valid schematron document. +XC0153 +Schema is not a valid RELAX NG grammar. + XC0155 Validity error in “$1”: $2. @@ -690,9 +759,15 @@ The format of the -D value must be “name=value”. xi:XI0071 $1 +xi:XI0072 +$1 + xi:XI0073 Inputs must be created with the same Processor that XML Calabash is using +xi:XI0998 +$1 + {http://xmlcalabash.com/ns/extensions}XI0999 It must be time for breakfast at Milliways, something impossible has happened: $1 diff --git a/src/main/scala/com/xmlcalabash/XMLCalabash.scala b/src/main/scala/com/xmlcalabash/XMLCalabash.scala index ad92fe5..7015815 100644 --- a/src/main/scala/com/xmlcalabash/XMLCalabash.scala +++ b/src/main/scala/com/xmlcalabash/XMLCalabash.scala @@ -4,7 +4,6 @@ import com.jafpl.exceptions.{JafplException, JafplLoopDetected} import com.jafpl.graph.{Binding, Node} import com.jafpl.messages.Message import com.jafpl.runtime.RuntimeConfiguration -import com.jafpl.steps.DataConsumer import com.jafpl.util.{ErrorListener, TraceEventManager} import com.xmlcalabash.XMLCalabash.loggedProcessorDetail import com.xmlcalabash.config.{DocumentManager, DocumentRequest, ErrorExplanation, XMLCalabashDebugOptions, XProcConfigurer} @@ -12,17 +11,18 @@ import com.xmlcalabash.exceptions.{ConfigurationException, ExceptionCode, ModelE import com.xmlcalabash.functions.FunctionImpl import com.xmlcalabash.messages.XdmValueItemMessage import com.xmlcalabash.model.util.{ExpressionParser, XProcConstants} -import com.xmlcalabash.model.xml.{DeclContainer, DeclareStep, Library, Parser, XMLContext} +import com.xmlcalabash.model.xxml.{XDeclareStep, XNameBinding, XParser, XStaticContext} import com.xmlcalabash.parsers.XPathParser -import com.xmlcalabash.runtime.{PrintingConsumer, SaxonExpressionEvaluator, StaticContext, XMLCalabashRuntime, XProcMetadata, XProcXPathExpression} +import com.xmlcalabash.runtime.params.XPathBindingParams +import com.xmlcalabash.runtime.{PrintingConsumer, SaxonExpressionEvaluator, StaticContext, XMLCalabashProcessor, XMLCalabashRuntime, XProcMetadata, XProcXPathExpression} import com.xmlcalabash.sbt.BuildInfo import com.xmlcalabash.util.{ArgBundle, DefaultErrorExplanation, DefaultXProcConfigurer, MediaType, PipelineBooleanOption, PipelineDocument, PipelineDocumentOption, PipelineDoubleOption, PipelineEnvironmentOption, PipelineEnvironmentOptionMap, PipelineEnvironmentOptionSerialization, PipelineExpressionOption, PipelineFileDocument, PipelineFilenameDocument, PipelineFunctionImplementation, PipelineInputDocument, PipelineInputFile, PipelineInputFilename, PipelineInputText, PipelineInputURI, PipelineInputXdm, PipelineNamespace, PipelineOption, PipelineOptionValue, PipelineOutputConsumer, PipelineOutputDocument, PipelineOutputFilename, PipelineOutputURI, PipelineParameter, PipelineStepImplementation, PipelineStringOption, PipelineSystemProperty, PipelineTextDocument, PipelineURIDocument, PipelineUntypedOption, PipelineUriOption, PipelineXdmDocument, PipelineXdmValueOption, URIUtils} import net.sf.saxon.lib.{ModuleURIResolver, UnparsedTextURIResolver} -import net.sf.saxon.s9api.{ItemType, Processor, QName, XdmAtomicValue, XdmNode, XdmValue} +import net.sf.saxon.s9api.{ItemType, ItemTypeFactory, Processor, QName, XdmAtomicValue, XdmNode, XdmValue} import org.slf4j.{Logger, LoggerFactory} import org.xml.sax.{EntityResolver, InputSource} -import java.io.{BufferedReader, ByteArrayInputStream, FileInputStream, FileReader} +import java.io.{ByteArrayInputStream, FileInputStream} import java.net.URI import java.nio.charset.StandardCharsets import javax.xml.transform.URIResolver @@ -64,10 +64,11 @@ object XMLCalabash { } } -class XMLCalabash private(userProcessor: Option[Processor], val configurer: XProcConfigurer) extends RuntimeConfiguration { +class XMLCalabash private(userProcessor: Option[Processor], val configurer: XProcConfigurer) extends XMLCalabashProcessor with RuntimeConfiguration { protected val logger: Logger = LoggerFactory.getLogger(this.getClass) private var _processor: Processor = _ + private var _itemTypeFactory: ItemTypeFactory = _ private var _expressionEvaluator: SaxonExpressionEvaluator = _ private val _collections = mutable.HashMap.empty[String, List[XdmNode]] private var _debugOptions: XMLCalabashDebugOptions = new XMLCalabashDebugOptions(this) @@ -87,18 +88,23 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro private var _staticBaseURI = URIUtils.cwdAsURI private var _locale = defaultLocale private var _episode = computeEpisode - private val _builtinSteps = ListBuffer.empty[Library] - private val _importedURIs = mutable.HashMap.empty[URI, DeclContainer] - // Do not allow the order to be random - private val _imports = ListBuffer.empty[URI] - private var _declaration = Option.empty[DeclareStep] + private var _declaration = Option.empty[XDeclareStep] private var _runtime: XMLCalabashRuntime = _ private var _pipeline = Option.empty[PipelineDocument] private val _inputs = mutable.HashMap.empty[String,ListBuffer[PipelineInputDocument]] private val _outputs = mutable.HashMap.empty[String,ListBuffer[PipelineOutputDocument]] private val _options = mutable.HashMap.empty[QName,PipelineOptionValue] + private val optionBindings = mutable.HashMap.empty[String,Message] + private val _staticOptions = mutable.HashMap.empty[QName, XdmValueItemMessage] + + private var _standardLibraryParser = false + private val _funcImplClasses = mutable.HashMap.empty[QName,String] + private val _stepImplClasses = mutable.HashMap.empty[QName,String] + // Only used during static analysis and not valid at runtime + private val _staticStepsAvailable = mutable.HashSet.empty[QName] + private var _staticStepsIndeterminate = true private var _except: Option[Exception] = None private var _longError = "" @@ -106,6 +112,37 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro val args = new ArgBundle() + protected[xmlcalabash] def standardLibraryParser: Boolean = _standardLibraryParser + protected[xmlcalabash] def standardLibraryParser_=(std: Boolean): Unit = { + _standardLibraryParser = std + } + + def externalSteps: Map[QName,String] = _stepImplClasses.toMap + + // Only used during static analysis and not valid at runtime + protected[xmlcalabash] def staticStepsIndeterminate: Boolean = _staticStepsIndeterminate + protected[xmlcalabash] def staticStepsIndeterminate_=(det: Boolean): Unit = { + _staticStepsIndeterminate = det + } + + // Only used during static analysis and not valid at runtime + protected[xmlcalabash] def staticStepsAvailable: Set[QName] = _staticStepsAvailable.toSet + protected[xmlcalabash] def staticStepsAvailable_=(aset: Set[QName]): Unit = { + _staticStepsAvailable.clear() + _staticStepsAvailable ++= aset + } + + protected[xmlcalabash] def staticStepAvailable(stepType: QName): Boolean = { + if (_stepImplClasses.contains(stepType) || _staticStepsAvailable.contains(stepType)) { + true + } else { + if (staticStepsIndeterminate) { + throw XProcException.xiIndeterminateSteps() + } + false + } + } + def inputs: Map[String,List[PipelineInputDocument]] = { val map = mutable.HashMap.empty[String,List[PipelineInputDocument]] for ((port,list) <- _inputs) { @@ -123,6 +160,13 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro } def options: Map[QName,PipelineOptionValue] = _options.toMap + def staticOptions: Map[QName, XdmValueItemMessage] = _staticOptions.toMap + def addStatic(name: QName, value: XdmValueItemMessage): Unit = { + if (_staticOptions.contains(name)) { + throw XProcException.xsShadowsStatic(name, None) + } + _staticOptions.put(name, value) + } def environmentOptions(name: QName): List[PipelineEnvironmentOption] = { parameters collect { case p: PipelineEnvironmentOption => p } filter { _.eqname == name.getEQName } @@ -163,8 +207,9 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro } } - val context = new XMLContext(this) - val map = mutable.HashMap.empty[QName,String] + _itemTypeFactory = new ItemTypeFactory(processor) + + val context = new XStaticContext() for (funcEnv <- parameters collect { case p: PipelineFunctionImplementation => p }) { val name = context.parseQName(funcEnv.eqname) try { @@ -175,7 +220,7 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro logger.warn(s"Failed to register ${name} with implementation ${funcEnv.className}; class implements ${func.getFunctionQName}") } else { _processor.registerExtensionFunction(func) - map.put(context.parseQName(funcEnv.eqname), funcEnv.className) + _funcImplClasses.put(context.parseQName(funcEnv.eqname), funcEnv.className) logger.debug(s"Registered ${name} with implementation ${funcEnv.className}") } } catch { @@ -187,7 +232,10 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro logger.warn(s"Failed to register ${name} with implementation ${funcEnv.className}: ${ex.getMessage}") } } - _funcImplClasses = Some(map.toMap) + + for (stepEnv <- parameters collect { case p: PipelineStepImplementation => p }) { + _stepImplClasses.put(context.parseQName(stepEnv.eqname), stepEnv.className) + } _expressionEvaluator = new SaxonExpressionEvaluator(this) configurer.xmlCalabashConfigurer.update(this) @@ -225,12 +273,27 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro } } + // Build the options before compiling in case some of them are statics... + _options.clear() + val nsmap = mutable.HashMap.empty[String,String] + for (param <- args.parameters) { + param match { + case ns: PipelineNamespace => + nsmap.put(ns.prefix, ns.namespace) + case opt: PipelineOption => + updateOptions(_options, nsmap.toMap, opt) + case _ => + () // Just ignore it? + } + } + _pipeline = args.pipeline if (_pipeline.isDefined && _declaration.isEmpty) { - val parser = new Parser(this) - val pipeline = try { - _pipeline.get match { + val parser = new XParser(this) + var pipeline: XDeclareStep = null + try { + pipeline = _pipeline.get match { case uri: PipelineURIDocument => parser.loadDeclareStep(uri.value) case str: PipelineFilenameDocument => @@ -251,24 +314,33 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro case _ => throw new RuntimeException("Unexpected pipeline input") } + if (parser.exceptions.nonEmpty) { + throw parser.exceptions.head + } } catch { case ex: XProcException => handleException(ex) ex.code match { case XProcException.err_xd0036 => - val value = ex.details.head.asInstanceOf[String] - val seqtype = ex.details(1).asInstanceOf[String] - throw XProcException.xsBadTypeValue(value, seqtype, ex.location) + // Weirdo remapping to satisfy the test suite + if (ex.variant == 1) { + val value = ex.details.head.asInstanceOf[String] + val seqtype = ex.details(1).asInstanceOf[String] + throw XProcException.xsBadTypeValue(value, seqtype, ex.location) + } case _ => - throw ex + () } - case ex: Throwable => + throw ex + case ex: Exception => throw ex } _declaration = Some(pipeline) close() + //println(_declaration.get.dump) + debugOptions.dumpTree(pipeline) debugOptions.dumpPipeline(pipeline) } @@ -301,7 +373,7 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro _inputs(in.port) += in - case out: PipelineOutputDocument => + case _: PipelineOutputDocument => () // See below case opt: PipelineOption => @@ -366,7 +438,7 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro } private def updateOptions(options: mutable.HashMap[QName,PipelineOptionValue], nsmap: Map[String,String], opt: PipelineOption): Unit = { - val context = new XMLContext(this, Some(URIUtils.cwdAsURI), nsmap, None) + val context = new XStaticContext(URIUtils.cwdAsURI, nsmap) val name = context.parseQName(opt.eqname) val value: XdmValue = opt match { case v: PipelineBooleanOption => new XdmAtomicValue(v.value) @@ -494,8 +566,23 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro runtime.output(port, pc) } - for ((name, value) <- _options) { - runtime.option(name, value.value, value.context) + for (option <- _declaration.get.options) { + if (_options.contains(option.name)) { + setOption(option, _options(option.name)) + } else { + if (option.required) { + throw XProcException.xsMissingRequiredOption(option.name, None) + } + if (option.usedByPipeline) { + setOption(option) + } + } + } + + for (name <- _options.keySet) { + if (!optionBindings.contains(name.getClarkName)) { + logger.info(s"Ignoring option '${name}'; it is not used by the pipeline") + } } runtime.run() @@ -519,6 +606,23 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro } } + private def setOption(option: XNameBinding): Unit = { + val eval = expressionEvaluator.newInstance() + val expr = new XProcXPathExpression(option.staticContext, option.select.getOrElse("()")) + val value = eval.compute(expr, List(), optionBindings.toMap, Map(), XPathBindingParams.EMPTY) + runtime.option(option.name, value, option.staticContext) + val msg = new XdmValueItemMessage(value, XProcMetadata.ANY, option.staticContext) + optionBindings.put(option.name.getClarkName, msg) + // FIXME: does the option value satisfy the type constraints? + } + + private def setOption(option: XNameBinding, value: PipelineOptionValue): Unit = { + runtime.option(option.name, value.value, value.context) + val msg = new XdmValueItemMessage(value.value, XProcMetadata.ANY, value.context) + optionBindings.put(option.name.getClarkName, msg) + // FIXME: does the option value satisfy the type constraints? + } + private def handleException(exception: Exception): Unit = { _except = Some(exception) @@ -608,7 +712,8 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro def runtime: XMLCalabashRuntime = _runtime def processor: Processor = _processor - def step: DeclareStep = _declaration.orNull + def itemTypeFactory: ItemTypeFactory = _itemTypeFactory + def step: XDeclareStep = _declaration.orNull def pipeline: PipelineDocument = _pipeline.orNull def errorMessage: String = _shortError @@ -684,30 +789,6 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro } } - protected[xmlcalabash] def builtinSteps: List[Library] = _builtinSteps.toList - protected[xmlcalabash] def builtinSteps_=(libs: List[Library]): Unit = { - if (_builtinSteps.nonEmpty) { - throw XProcException.xiThisCantHappen("Attempt to redefine builtin steps", None) - } - _builtinSteps ++= libs - } - - protected[xmlcalabash] def importedURIs: List[URI] = _imports.toList - protected[xmlcalabash] def importedURI(href: URI): Option[DeclContainer] = { - _importedURIs.get(href) - } - protected[xmlcalabash] def addImportedURI(href: URI, container: DeclContainer): Unit = { - if (_importedURIs.contains(href)) { - throw new RuntimeException(s"Attempt to redefine imported uri: $href") - } - _importedURIs.put(href, container) - _imports += href - } - protected[xmlcalabash] def clearImportedURIs(): Unit = { - _importedURIs.clear() - _imports.clear() - } - def debugOptions: XMLCalabashDebugOptions = _debugOptions def debugOptions_=(options: XMLCalabashDebugOptions): Unit = { _debugOptions = options @@ -841,7 +922,7 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro def defaultSerializationOptions(contentType: String): Map[QName,String] = { if (_defaultSerializationOptions.isEmpty) { checkClosed() - val context = new XMLContext(this) + val context = new XStaticContext() val map = mutable.HashMap.empty[String,Map[QName,String]] for (ser <- parameters collect { case p: PipelineEnvironmentOptionSerialization => p }) { val smap = mutable.HashMap.empty[QName, String] @@ -871,28 +952,6 @@ class XMLCalabash private(userProcessor: Option[Processor], val configurer: XPro _episode = episode } - // ============================================================================================== - private var _stepImplClasses: Option[Map[QName,String]] = None - private var _funcImplClasses: Option[Map[QName,String]] = None - - def atomicStepImplementation(stepType: QName): Option[String] = { - if (_stepImplClasses.isEmpty) { - checkClosed() - val context = new XMLContext(this) - val map = mutable.HashMap.empty[QName,String] - for (step <- parameters collect { case p: PipelineStepImplementation => p }) { - map.put(context.parseQName(step.eqname), step.className) - } - _stepImplClasses = Some(map.toMap) - } - _stepImplClasses.get.get(stepType) - } - - def functionImplementation(funcName: QName): Option[String] = { - // Never empty because it's initialized when the configuration is closed. - _funcImplClasses.get.get(funcName) - } - // FIXME: Should this be a factory, or should XPathParser be reusable? def expressionParser: ExpressionParser = { new XPathParser(this) diff --git a/src/main/scala/com/xmlcalabash/config/OptionSignature.scala b/src/main/scala/com/xmlcalabash/config/OptionSignature.scala index 87647f4..1888e91 100644 --- a/src/main/scala/com/xmlcalabash/config/OptionSignature.scala +++ b/src/main/scala/com/xmlcalabash/config/OptionSignature.scala @@ -1,32 +1,33 @@ package com.xmlcalabash.config +import com.xmlcalabash.model.xxml.XOption import net.sf.saxon.s9api.{QName, SequenceType, XdmAtomicValue} import scala.collection.mutable.ListBuffer -class OptionSignature(val name: QName) { +class OptionSignature private(val name: QName) { private var _required = true + private var _static = false + private var _as = "xs:untypedAtomic" private var _declaredType = Option.empty[SequenceType] private var _occurrence = Option.empty[String] private var _tokenList: Option[ListBuffer[XdmAtomicValue]] = None private var _defaultValue = Option.empty[String] private var _forceQNameKeys = false - def this(name: QName, optType: SequenceType, required: Boolean) = { - this(name) - _declaredType = Some(optType) - _required = required + def this(option: XOption) = { + this(option.name) + _as = option.as.getOrElse("xs:untypedAtomic") + _declaredType = option.declaredType + _required = option.required + _static = option.static } def required: Boolean = _required - def required_=(req: Boolean): Unit = { - _required = req - } + def static: Boolean = _static + def as: String = _as def declaredType: Option[SequenceType] = _declaredType - def declaredType_=(value: SequenceType): Unit = { - _declaredType = Some(value) - } def occurrence: Option[String] = _occurrence def occurrence_=(value: String): Unit = { diff --git a/src/main/scala/com/xmlcalabash/config/PortSignature.scala b/src/main/scala/com/xmlcalabash/config/PortSignature.scala index b8babe5..07557e6 100644 --- a/src/main/scala/com/xmlcalabash/config/PortSignature.scala +++ b/src/main/scala/com/xmlcalabash/config/PortSignature.scala @@ -1,6 +1,6 @@ package com.xmlcalabash.config -import com.xmlcalabash.model.xml.DataSource +import com.xmlcalabash.model.xxml.XDataSource import com.xmlcalabash.util.MediaType import scala.collection.mutable.ListBuffer @@ -9,7 +9,7 @@ class PortSignature(val port: String) { private var _cardinality = "1" private var _primary = Option.empty[Boolean] private val _contentTypes = ListBuffer.empty[MediaType] - private val _defaultBindings = ListBuffer.empty[DataSource] + private val _defaultBindings = ListBuffer.empty[XDataSource] def this(port: String, primary: Boolean, sequence: Boolean) = { this(port) @@ -19,7 +19,7 @@ class PortSignature(val port: String) { } } - def this(port: String, primary: Boolean, sequence: Boolean, bindings: List[DataSource]) = { + def this(port: String, primary: Boolean, sequence: Boolean, bindings: List[XDataSource]) = { this(port, primary, sequence) _defaultBindings ++= bindings } @@ -42,5 +42,5 @@ class PortSignature(val port: String) { _contentTypes ++= types } - def defaultBindings: List[DataSource] = _defaultBindings.toList + def defaultBindings: List[XDataSource] = _defaultBindings.toList } diff --git a/src/main/scala/com/xmlcalabash/config/StepSignature.scala b/src/main/scala/com/xmlcalabash/config/StepSignature.scala index 07b6ad7..123fc95 100644 --- a/src/main/scala/com/xmlcalabash/config/StepSignature.scala +++ b/src/main/scala/com/xmlcalabash/config/StepSignature.scala @@ -1,8 +1,8 @@ package com.xmlcalabash.config import com.jafpl.graph.Location -import com.xmlcalabash.exceptions.{ExceptionCode, ModelException} -import com.xmlcalabash.model.xml.DeclareStep +import com.xmlcalabash.exceptions.{ExceptionCode, ModelException, XProcException} +import com.xmlcalabash.model.xxml.XDeclareStep import net.sf.saxon.s9api.QName import scala.collection.mutable @@ -13,7 +13,7 @@ class StepSignature(val stepType: Option[QName]) { private val _outputPorts = mutable.HashMap.empty[String, PortSignature] private val _options = ListBuffer.empty[OptionSignature] private val _implementation = ListBuffer.empty[String] - private var _declaration = Option.empty[DeclareStep] + private var _declaration = Option.empty[XDeclareStep] def addInput(port: PortSignature, location: Location): Unit = { if (_inputPorts.contains(port.port)) { @@ -47,8 +47,8 @@ class StepSignature(val stepType: Option[QName]) { _implementation += className } - def declaration: Option[DeclareStep] = _declaration - def declaration_=(decl: DeclareStep): Unit = { + def declaration: Option[XDeclareStep] = _declaration + def declaration_=(decl: XDeclareStep): Unit = { if (_implementation.nonEmpty) { throw new RuntimeException("Cannot have an atomic step with the same name as a non-atomic step") } @@ -91,13 +91,13 @@ class StepSignature(val stepType: Option[QName]) { } } - def option(name: QName, location: Option[Location]): OptionSignature = { + def option(name: QName): Option[OptionSignature] = { for (opt <- _options) { if (opt.name == name) { - return opt + return Some(opt) } } - throw new ModelException(ExceptionCode.BADOPTSIG, List(stepType.toString, name.toString), location) + None } def primaryInput: Option[PortSignature] = { @@ -119,11 +119,7 @@ class StepSignature(val stepType: Option[QName]) { } override def toString: String = { - if (stepType.isDefined) { - stepType.get.toString - } else { - "anonymous StepSignature" - } + stepType.toString } } diff --git a/src/main/scala/com/xmlcalabash/config/XMLCalabashDebugOptions.scala b/src/main/scala/com/xmlcalabash/config/XMLCalabashDebugOptions.scala index 6a2122e..0543e93 100644 --- a/src/main/scala/com/xmlcalabash/config/XMLCalabashDebugOptions.scala +++ b/src/main/scala/com/xmlcalabash/config/XMLCalabashDebugOptions.scala @@ -1,18 +1,18 @@ package com.xmlcalabash.config -import java.io.{ByteArrayInputStream, ByteArrayOutputStream, File, FileOutputStream, PrintStream, PrintWriter} import com.jafpl.graph.Graph import com.xmlcalabash.XMLCalabash import com.xmlcalabash.config.XMLCalabashDebugOptions.{GRAPH, OPENGRAPH, PIPELINE, TREE} -import com.xmlcalabash.model.xml.DeclareStep import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.model.xxml.XDeclareStep import com.xmlcalabash.util.PipelineEnvironmentOption - -import javax.xml.transform.sax.SAXSource import net.sf.saxon.s9api.{QName, XdmDestination, XdmNode} import org.slf4j.{Logger, LoggerFactory} import org.xml.sax.InputSource +import java.io.{ByteArrayInputStream, ByteArrayOutputStream, File, FileOutputStream, PrintWriter} +import java.nio.charset.StandardCharsets +import javax.xml.transform.sax.SAXSource import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -28,8 +28,8 @@ class XMLCalabashDebugOptions(config: XMLCalabash) { private val _injectables = ListBuffer.empty[String] - private val dumped = mutable.HashMap.empty[DeclareStep, mutable.HashSet[String]] - private val dumpCount = mutable.HashMap.empty[DeclareStep, mutable.HashMap[String, Long]] + private val dumped = mutable.HashMap.empty[XDeclareStep, mutable.HashSet[String]] + private val dumpCount = mutable.HashMap.empty[XDeclareStep, mutable.HashMap[String, Long]] def injectables: List[String] = _injectables.toList @@ -43,12 +43,18 @@ class XMLCalabashDebugOptions(config: XMLCalabash) { } def graphviz_dot: Option[String] = { - val envOpt = environmentOptions(XProcConstants.cc_graphviz).headOption - if (envOpt.isDefined && envOpt.head.getString.isDefined) { - envOpt.head.getString - } else { - None + var dot = Option.empty[String] + for (option <- environmentOptions(XProcConstants.cc_graphviz)) { + if (dot.isEmpty && option.getString.isDefined) { + for (path <- option.getString.get.split("\\s+")) { + val exec = new File(path) + if (exec.exists() && exec.canExecute) { + dot = Some(path) + } + } + } } + dot } def run: Boolean = { @@ -107,27 +113,27 @@ class XMLCalabashDebugOptions(config: XMLCalabash) { // =========================================================================================== - def dumpTree(decl: DeclareStep): Unit = { + def dumpTree(decl: XDeclareStep): Unit = { dump(decl, TREE) } - def dumpPipeline(decl: DeclareStep): Unit = { + def dumpPipeline(decl: XDeclareStep): Unit = { dump(decl, PIPELINE) } - def dumpGraph(decl: DeclareStep, graph: Graph): Unit = { + def dumpGraph(decl: XDeclareStep, graph: Graph): Unit = { dump(decl, GRAPH, Some(graph)) } - def dumpOpenGraph(decl: DeclareStep, graph: Graph): Unit = { + def dumpOpenGraph(decl: XDeclareStep, graph: Graph): Unit = { dump(decl, OPENGRAPH, Some(graph)) } - private def dump(decl: DeclareStep, opt: String): Unit = { + private def dump(decl: XDeclareStep, opt: String): Unit = { dump(decl, opt, None) } - private def dump(decl: DeclareStep, opt: String, graph: Option[Graph]): Unit = { + private def dump(decl: XDeclareStep, opt: String, graph: Option[Graph]): Unit = { if (!graph_option(opt)) { return } @@ -161,7 +167,7 @@ class XMLCalabashDebugOptions(config: XMLCalabash) { val fn = s"$outputDirectory/$basefn$ext.xml" val fos = new FileOutputStream(new File(fn)) val pw = new PrintWriter(fos) - pw.write(decl.xdump.toString) + pw.write(decl.dump.toString) pw.close() fos.close() case PIPELINE => @@ -169,14 +175,18 @@ class XMLCalabashDebugOptions(config: XMLCalabash) { val fn = s"$outputDirectory/$basefn$ext.svg" val baos = new ByteArrayOutputStream() val pw = new PrintWriter(baos) - pw.write(decl.xdump.toString) + pw.write(decl.dump.toString) pw.close() + //println("********* PIPELINE ************") + //println(baos.toString(StandardCharsets.UTF_8)) svgPipeline(fn, baos) case GRAPH => val basefn = s"${name}_graph" val fn = s"$outputDirectory/$basefn$ext.svg" val baos = new ByteArrayOutputStream() val pw = new PrintWriter(baos) + //println("********* GRAPH ************") + //println(graph.get.asXML.toString) pw.write(graph.get.asXML.toString) pw.close() svgGraph(fn, baos, "pgx2dot.xsl") @@ -188,6 +198,8 @@ class XMLCalabashDebugOptions(config: XMLCalabash) { val pw = new PrintWriter(baos) pw.write(graph.get.asXML.toString) pw.close() + //println("********* OPEN GRAPH ************") + //println(baos.toString(StandardCharsets.UTF_8)) svgGraph(fn, baos, "pg2dot.xsl") } } diff --git a/src/main/scala/com/xmlcalabash/exceptions/XProcException.scala b/src/main/scala/com/xmlcalabash/exceptions/XProcException.scala index e7b90ee..7583acc 100644 --- a/src/main/scala/com/xmlcalabash/exceptions/XProcException.scala +++ b/src/main/scala/com/xmlcalabash/exceptions/XProcException.scala @@ -4,7 +4,7 @@ import com.jafpl.exceptions.{JafplException, JafplExceptionCode} import com.jafpl.graph.Location import com.jafpl.messages.{Message, Metadata} import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.model.xml.Artifact +import com.xmlcalabash.model.xxml.XArtifact import com.xmlcalabash.runtime.{StaticContext, XProcExpression} import com.xmlcalabash.util.MediaType import net.sf.saxon.om.StructuredQName @@ -14,11 +14,14 @@ import java.net.URI import scala.collection.mutable.ListBuffer object XProcException { + val err_stepAvailableIndeterminate = new QName("cx", XProcConstants.ns_cx, "XI0100") + val err_xd0011 = new QName("err", XProcConstants.ns_err, "XD0011") val err_xd0015 = new QName("err", XProcConstants.ns_err, "XD0015") val err_xd0030 = new QName("err", XProcConstants.ns_err, "XD0030") val err_xd0036 = new QName("err", XProcConstants.ns_err, "XD0036") val err_xd0038 = new QName("err", XProcConstants.ns_err, "XD0038") + val err_XD0045 = new QName("err", XProcConstants.ns_err, "XD0045") val err_xd0057 = new QName("err", XProcConstants.ns_err, "XD0057") val err_xd0059 = new QName("err", XProcConstants.ns_err, "XD0059") val err_xd0072 = new QName("err", XProcConstants.ns_err, "XD0072") @@ -28,6 +31,18 @@ object XProcException { var cx_XI0073 = new QName("cx", XProcConstants.ns_cx, "XI0073") + def errxs(code: Int): QName = { + new QName("err", XProcConstants.ns_err, f"XS$code%04d") + } + + def errxd(code: Int): QName = { + new QName("err", XProcConstants.ns_err, f"XD$code%04d") + } + + def errxi(code: Int): QName = { + new QName("cx", XProcConstants.ns_cx, f"XI$code%04d") + } + def xtde(errNo: Int): StructuredQName = { new StructuredQName("err", XProcConstants.ns_xqt_errors, f"XTDE$errNo%04d") } @@ -55,8 +70,8 @@ object XProcException { def xiInjectMessageNodes(location: Option[Location]): XProcException = internalError(17, location) def xiInjectRedefPort(location: Option[Location]): XProcException = internalError(18, location) def xiChildNotFound(location: Option[Location]): XProcException = internalError(19, location) - def xiBadPatch(node: Artifact, location: Option[Location]): XProcException = internalError(20, location, node) - def xiBadPatchChild(node: Artifact, location: Option[Location]): XProcException = internalError(21, location, node) + def xiBadPatch(node: XArtifact, location: Option[Location]): XProcException = internalError(20, location, node) + def xiBadPatchChild(node: XArtifact, location: Option[Location]): XProcException = internalError(21, location, node) def xiBadMessage(message: Message, location: Option[Location]): XProcException = internalError(22, location, message) def xiInvalidPort(port: String, location: Option[Location]): XProcException = internalError(23, location, port) def xiInvalidPropertyValue(value: Any, location: Option[Location]): XProcException = internalError(24, location, value) @@ -112,7 +127,12 @@ object XProcException { def xiWrongProcessor(message: String, location: Option[Location]): XProcException = internalError(73, location, message) def xiBadMediaType(message: String, location: Option[Location]): XProcException = internalError(74, location, message) def xiBadConsumers(message: String, location: Option[Location]): XProcException = internalError(75, location, message) + def xiImpossibleNamespaceConfiguration(prefix: String, uri: String, location: Option[Location]): XProcException = internalError(76, location, List(prefix,uri)) + def xiIndeterminateSteps(): XProcException = internalError(100, None) + def xiStepAvailableForbidden(): XProcException = internalError(101, None) + + def xiUserError(msg: String): XProcException = internalError(998, None, msg) def xiThisCantHappen(msg: String): XProcException = internalError(999, None, msg) def xiThisCantHappen(msg: String, location: Option[Location]): XProcException = internalError(999, location, msg) @@ -130,7 +150,6 @@ object XProcException { def xdContextItemAbsent(expr: String, msg: String, location: Option[Location]): XProcException = dynamicError(1, List(expr, msg), location) // FIXME: subtypes - def xdBadValue(value: String, vtype: String, location: Option[Location]): XProcException = dynamicError((19, 1), List(value,vtype), location) def xdBadMatchPattern(pattern: String, message: String, location: Option[Location]): XProcException = dynamicError((19, 2), List(pattern, message), location) def xdBadVisibility(visibility: String, location: Option[Location]): XProcException = dynamicError((19, 3), visibility, location) def xdBadValue(value: String, location: Option[Location]): XProcException = dynamicError((19, 4), value, location) @@ -145,8 +164,10 @@ object XProcException { def xdStepFailed(msg: String, location: Option[Location]): XProcException = dynamicError(30, msg, location) def xdConflictingNamespaceDeclarations(msg: String, location: Option[Location]): XProcException = dynamicError(34, msg, location) - def xdBadType(value: String, as: String, location: Option[Location]): XProcException = dynamicError(36, List(value, as), location) - def xdBadType(name: QName, value: String, as: String, location: Option[Location]): XProcException = dynamicError(36, List(name, value, as), location) + + def xdBadType(value: String, as: String, location: Option[Location]): XProcException = dynamicError((36,1), List(value, as), location) + def xdBadType(name: QName, value: String, as: String, location: Option[Location]): XProcException = dynamicError((36,2), List(name, value, as), location) + def xdBadInputMediaType(ctype: MediaType, allowed: List[MediaType], location: Option[Location]): XProcException = dynamicError(38, List(ctype, allowed), location) def xdUnsupportedCharset(charset: String, location: Option[Location]): XProcException = dynamicError(39, charset, location) def xdIncorrectEncoding(encoding: String, location: Option[Location]): XProcException = dynamicError(40, encoding, location) @@ -188,17 +209,20 @@ object XProcException { def xsBadAttribute(name: QName, location: Option[Location]): XProcException = staticError(8, name, location) def xsBadPortName(stepType: QName, port: String, location: Option[Location]): XProcException = staticError(10, List(stepType, port), location) def xsDupPortName(port: String, location: Option[Location]): XProcException = staticError(11, port, location) + def xsDupPrimaryOutputPort(port: String, primaryPort: String, location: Option[Location]): XProcException = staticError(14, List(port, primaryPort), location) def xsRequiredAndDefaulted(name: QName, location: Option[Location]): XProcException = staticError(17, name, location) def xsMissingRequiredOption(optName: QName, location: Option[Location]): XProcException = staticError(18, optName, location) - def xsPortNotReadableNoStep(step: String, location: Option[Location]): XProcException = staticError((22,1), step, location) + def xsPortNotReadableNoStep(port: String, location: Option[Location]): XProcException = staticError((22,1), port, location) def xsPortNotReadableNoPrimaryInput(step: String, location: Option[Location]): XProcException = staticError((22,2), step, location) def xsPortNotReadableNoPrimaryOutput(step: String, location: Option[Location]): XProcException = staticError((22,3), step, location) def xsPortNotReadable(step: String, port: String, location: Option[Location]): XProcException = staticError((22,4), List(step, port), location) + def xsBadStepTypeNamespace(location: Option[Location]): XProcException = staticError((25,1), location) + def xsBadStepTypeNamespace(uri: String, location: Option[Location]): XProcException = staticError((25,2), uri, location) def xsOptionInXProcNamespace(name: QName, location: Option[Location]): XProcException = staticError(28, name, location) - def xsDupPrimaryPort(port: String, primaryPort: String, location: Option[Location]): XProcException = staticError(30, List(port, primaryPort), location) + def xsDupPrimaryInputPort(port: String, primaryPort: String, location: Option[Location]): XProcException = staticError(30, List(port, primaryPort), location) def xsUndeclaredOption(stepType: QName, optName: QName, location: Option[Location]): XProcException = staticError(31, List(stepType,optName), location) def xsUnconnectedPrimaryInputPort(step: String, port: String, location: Option[Location]): XProcException = staticError(32, List(step,port), location) def xsDupStepType(stepType: QName, location: Option[Location]): XProcException = staticError(36, stepType, location) @@ -210,6 +234,11 @@ object XProcException { def xsImportFailed(href: URI, location: Option[Location]): XProcException = staticError((52,1), href, location) def xsBadImport(name: QName, location: Option[Location]): XProcException = staticError((52,2), name, location) def xsStepTypeRequired(location: Option[Location]): XProcException = staticError(53, List(), location) + def xsBadExcludeInlinePrefixes(): XProcException = staticError((57,1), List(), None) + def xsBadExcludeInlinePrefixes(prefix: String): XProcException = staticError((57,2), prefix, None) + def xsBadExcludeInlinePrefixesDefault(): XProcException = staticError(58, List(), None) + def xsNotAPipeline(): XProcException = staticError((59,1), None) + def xsNotAPipeline(name: QName): XProcException = staticError((59,2), name, None) def xsInvalidVersion(version: Double, location: Option[Location]): XProcException = staticError(60, version, location) def xsVersionRequired(location: Option[Location]): XProcException = staticError(62, location) def xsBadVersion(version: String, location: Option[Location]): XProcException = staticError(63, version, location) @@ -227,11 +256,11 @@ object XProcException { def xsMissingWhen(location: Option[Location]): XProcException = staticError(74, location) def xsInvalidTryCatch(msg: String, location: Option[Location]): XProcException = staticError(75, msg, location) - def xsBadTypeValue(value: String, reqdType: String, location: Option[Location]): XProcException = staticError(77, List(value, reqdType), location) + def xsBadTypeValue(value: String, reqdType: String, location: Option[Location]): XProcException = staticError((77,1), List(value, reqdType), location) + def xsBadTypeEmpty(value: String, location: Option[Location]): XProcException = staticError((77,2), value, location) + def xsBadVisibility(value: String, location: Option[Location]): XProcException = staticError((77,3), value, location) - def xsInlineCommentNotAllowed(comment: String, location: Option[Location]): XProcException = staticError((79,1), comment, location) - def xsInlinePiNotAllowed(pi: String, location: Option[Location]): XProcException = staticError((79,2), pi, location) - def xsInlineTextNotAllowed(text: String, location: Option[Location]): XProcException = staticError((79,3), text, location) + def xsInlineNotAllowed(location: Option[Location]): XProcException = staticError(79, location) def xsDupWithOptionName(optName: QName, location: Option[Location]): XProcException = staticError(80, optName, location) @@ -242,36 +271,44 @@ object XProcException { def xsPipeAndHref(location: Option[Location]): XProcException = staticError(85, location) def xsDupWithInputPort(port: String, location: Option[Location]): XProcException = staticError(86, port, location) def xsOptionUndeclaredNamespace(name: String, location: Option[Location]): XProcException = staticError(87, name, location) - def xsTvtForbidden(location: Option[Location]): XProcException = staticError(88, location) + def xsShadowsStatic(name: QName, location: Option[Location]): XProcException = staticError(88, name, location) def xsNoSiblingsOnEmpty(location: Option[Location]): XProcException = staticError(89, None, location) def xsInvalidPipeToken(token: String, location: Option[Location]): XProcException = staticError(90, token, location) + def xsRedeclareStatic(name: QName, location: Option[Location]): XProcException = staticError(92, name, location) + def xsNoSelectOnStaticOption(location: Option[Location]): XProcException = staticError(93, None, location) def xsNoSelectOnVariable(location: Option[Location]): XProcException = staticError(94, None, location) + def xsRequiredAndStatic(name: QName, location: Option[Location]): XProcException = staticError(95, name, location) def xsInvalidSequenceType(seqType: String, errMsg: String, location: Option[Location]): XProcException = staticError(96, List(seqType, errMsg), location) - def xsXProcNamespaceError(attrName: QName, location: Option[Location]): XProcException = staticError(97, attrName, location) + def xsXProcNamespaceError(attrName: QName, elemName: QName, location: Option[Location]): XProcException = staticError(97, List(attrName, elemName), location) //def xsElementNotAllowed(element: QName, message: String, location: Option[Location]): XProcException = staticError(100, List(element, message), location) def xsInvalidPipeline(message: String, location: Option[Location]): XProcException = staticError((100,1), message, location) def xsElementNotAllowed(element: QName, location: Option[Location]): XProcException = staticError((100,2), List(element, "element is not allowed here"), location) + def xsCantMixConnectionsWithImplicitInlines(name: QName, location: Option[Location]): XProcException = staticError((100,3), name, location) def xsInvalidValues(values: String, location: Option[Location]): XProcException = staticError(101, values, location) - def xsBadChooseOutputs(primary: String, alsoPrimary: String, location: Option[Location]): XProcException = staticError((102,1), List(primary,alsoPrimary), location) - def xsBadTryOutputs(primary: String, alsoPrimary: String, location: Option[Location]): XProcException = staticError((102,2), List(primary,alsoPrimary), location) - - def xsMissingRequiredInput(port: String, location: Option[Location]): XProcException = staticError(998, port, location) - - def xsXProcElementNotAllowed(name: String, location: Option[Location]): XProcException = staticError(100, name, location) + def xsBadChooseOutputs(primary: String, location: Option[Location]): XProcException = staticError((102,1), primary, location) + def xsBadChooseOutputs(primary: String, alsoPrimary: String, location: Option[Location]): XProcException = staticError((102,2), List(primary,alsoPrimary), location) + def xsBadTryOutputs(primary: String, location: Option[Location]): XProcException = staticError((102,3), primary, location) + def xsBadTryOutputs(primary: String, alsoPrimary: String, location: Option[Location]): XProcException = staticError((102,4), List(primary,alsoPrimary), location) def xsNoBindingInExpression(name: QName, location: Option[Location]): XProcException = staticError((107,1), name, location) def xsStaticErrorInExpression(expr: String, msg: String, location: Option[Location]): XProcException = staticError((107,2), List(expr, msg), location) def xsStaticRefsContext(msg: String, location: Option[Location]): XProcException = staticError((107,3), msg, location) def xsStaticRefsNonStatic(name: QName, location: Option[Location]): XProcException = staticError((107,4), name, location) def xsStaticRefsNonStaticStr(name: String, location: Option[Location]): XProcException = staticError((107,4), name, location) + def xsAtomicOutputWithBinding(port: String, stepType: QName, location: Option[Location]): XProcException = staticError((107,5), List(port, stepType), location) + def xsAtomicOutputWithBinding(port: String, location: Option[Location]): XProcException = staticError((107,6), port, location) def xsPrimaryOutputRequired(location: Option[Location]): XProcException = staticError(108, location) + def xsOptionMustBeStatic(name: QName, location: Option[Location]): XProcException = staticError(109, name, location) def xsUnrecognizedContentTypeShortcut(ctype: String, location: Option[Location]): XProcException = staticError(111, ctype, location) def xsPrimaryOutputOnFinally(port: String, location: Option[Location]): XProcException = staticError(112, port, location) + def xsInvalidExpandText(name: QName, value: String, location: Option[Location]): XProcException = staticError((113,1), List(name, value), location) + def xsNoSuchPort(port: String, stepType: QName, location: Option[Location]): XProcException = staticError(114, List(port, stepType), location) + def xsUseWhenDeadlock(expr: String, location: Option[Location]): XProcException = staticError((115,1), expr, location) def xcGeneralException(code: QName, errors: Option[XdmNode], location: Option[Location]): XProcException = { val except = new XProcException(code, 1, None, location, List()) @@ -387,6 +424,7 @@ object XProcException { def xcOverrideContentTypesBadRegex(regex: String, location: Option[Location]): XProcException = stepError(147, regex, location) def xcFileMoveBadScheme(uri: URI, location: Option[Location]): XProcException = stepError(148, uri, location) def xcNotASchematronDocument(): XProcException = stepError(151, None) + def xcNotRelaxNG(href: String, message: String, location: Option[Location]): XProcException = stepError(153, List(href, message), location) def xcNotSchemaValidRelaxNG(href: String, message: String, location: Option[Location]): XProcException = stepError(155, List(href, message), location) def xcNotSchemaValidXmlSchema(href: String, message: String, location: Option[Location]): XProcException = stepError((156,1), List(href, message), location) def xcNotSchemaValidXmlSchema(href: String, line: Long, col: Long, message: String, location: Option[Location]): XProcException = stepError((156,2), List(href, line, col, message), location) diff --git a/src/main/scala/com/xmlcalabash/functions/DocumentProperties.scala b/src/main/scala/com/xmlcalabash/functions/DocumentProperties.scala index 51fb322..dae6715 100644 --- a/src/main/scala/com/xmlcalabash/functions/DocumentProperties.scala +++ b/src/main/scala/com/xmlcalabash/functions/DocumentProperties.scala @@ -68,11 +68,14 @@ class DocumentProperties(runtime: XMLCalabash) extends FunctionImpl { map = map.put(new XdmAtomicValue(XProcConstants._base_uri), value) } case _ => + map = map.put(new XdmAtomicValue(key), value) +/* if (key.getNamespaceURI == "") { map = map.put(new XdmAtomicValue(key.getLocalName), value) } else { map = map.put(new XdmAtomicValue(key), value) } + */ } } diff --git a/src/main/scala/com/xmlcalabash/functions/StepAvailable.scala b/src/main/scala/com/xmlcalabash/functions/StepAvailable.scala index ca9fb19..549f599 100644 --- a/src/main/scala/com/xmlcalabash/functions/StepAvailable.scala +++ b/src/main/scala/com/xmlcalabash/functions/StepAvailable.scala @@ -3,13 +3,13 @@ package com.xmlcalabash.functions import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.model.xml.DeclareStep +import com.xmlcalabash.model.xxml.{XArtifact, XDeclareStep} import net.sf.saxon.`type`.BuiltInAtomicType import net.sf.saxon.expr.{Expression, StaticContext, XPathContext} import net.sf.saxon.lib.{ExtensionFunctionCall, ExtensionFunctionDefinition} import net.sf.saxon.om.{Item, Sequence, StructuredQName} import net.sf.saxon.s9api.QName -import net.sf.saxon.value.{BooleanValue, Int64Value, SequenceType} +import net.sf.saxon.value.{BooleanValue, SequenceType} class StepAvailable(runtime: XMLCalabash) extends FunctionImpl() { private val funcname = new StructuredQName("p", XProcConstants.ns_p, "step-available") @@ -34,41 +34,41 @@ class StepAvailable(runtime: XMLCalabash) extends FunctionImpl() { override def call(context: XPathContext, arguments: Array[Sequence]): Sequence = { val exprEval = runtime.expressionEvaluator - if (exprEval.dynContext == null) { + if (exprEval.dynContext.isEmpty) { throw XProcException.xiExtFunctionNotAllowed() } - val lexicalQName = arguments(0).head().asInstanceOf[Item].getStringValue + val lexicalQName = arguments(0).head().getStringValue val propertyName = if (lexicalQName.trim.startsWith("Q{")) { StructuredQName.fromClarkName(lexicalQName) } else { StructuredQName.fromLexicalQName(lexicalQName, false, false, staticContext.getNamespaceResolver) } - var available = false - - // This is a bit of a hack; it relies on artifact having been passed to the - // dynamic context and I'm not confident that's always going to have happened. - // But it appears to have happened in the common case. - var art = exprEval.dynContext.get.artifact - while (art.isDefined && !art.get.isInstanceOf[DeclareStep]) { - art = art.get.parent + val stepType = new QName(propertyName.getPrefix, propertyName.getURI, propertyName.getLocalPart) + if (runtime.externalSteps.contains(stepType)) { + return new BooleanValue(true, BuiltInAtomicType.BOOLEAN) } - if (art.isDefined) { - val decl = art.get.asInstanceOf[DeclareStep] - val qname = new QName(propertyName.getPrefix, propertyName.getURI, propertyName.getLocalPart) - val sig = decl.declaration(qname) - if (sig.isDefined) { - val impl = sig.get.implementation - if (impl.isEmpty) { - available = !sig.get.declaration.get.atomic - } else { - available = true + + val dc = exprEval.dynContext.get + if (dc.artifact.isEmpty) { + new BooleanValue(runtime.staticStepAvailable(stepType), BuiltInAtomicType.BOOLEAN) + } else { + var p: Option[XArtifact] = dc.artifact + while (p.isDefined) { + p.get match { + case decl: XDeclareStep => + for (step <- decl.inScopeSteps) { + if (step.stepType.isDefined && step.stepType.get == stepType) { + return new BooleanValue(!step.atomic || step.implementationClass.isDefined, BuiltInAtomicType.BOOLEAN) + } + } + case _ => () } + p = p.get.parent } + new BooleanValue(false, BuiltInAtomicType.BOOLEAN) } - - new BooleanValue(available, BuiltInAtomicType.BOOLEAN) } } } diff --git a/src/main/scala/com/xmlcalabash/messages/AnyItemMessage.scala b/src/main/scala/com/xmlcalabash/messages/AnyItemMessage.scala index 6db9bbf..2e8e3af 100644 --- a/src/main/scala/com/xmlcalabash/messages/AnyItemMessage.scala +++ b/src/main/scala/com/xmlcalabash/messages/AnyItemMessage.scala @@ -1,13 +1,14 @@ package com.xmlcalabash.messages -import com.xmlcalabash.runtime.{BinaryNode, StaticContext, XProcMetadata} +import com.xmlcalabash.runtime.{BinaryNode, XProcMetadata} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.XdmNode import org.slf4j.{Logger, LoggerFactory} class AnyItemMessage(override val item: XdmNode, private val binary: BinaryNode, override val metadata: XProcMetadata, - override val context: StaticContext) extends XProcItemMessage(item, metadata, context) { + override val context: MinimalStaticContext) extends XProcItemMessage(item, metadata, context) { protected val logger: Logger = LoggerFactory.getLogger(this.getClass) def shadow: BinaryNode = { diff --git a/src/main/scala/com/xmlcalabash/messages/XProcItemMessage.scala b/src/main/scala/com/xmlcalabash/messages/XProcItemMessage.scala index bc52025..9e91908 100644 --- a/src/main/scala/com/xmlcalabash/messages/XProcItemMessage.scala +++ b/src/main/scala/com/xmlcalabash/messages/XProcItemMessage.scala @@ -1,10 +1,11 @@ package com.xmlcalabash.messages import com.jafpl.messages.ItemMessage -import com.xmlcalabash.runtime.{StaticContext, XProcMetadata} +import com.xmlcalabash.runtime.XProcMetadata +import com.xmlcalabash.util.MinimalStaticContext class XProcItemMessage(override val item: Any, override val metadata: XProcMetadata, - val context: StaticContext) + val context: MinimalStaticContext) extends ItemMessage(item, metadata) { } diff --git a/src/main/scala/com/xmlcalabash/messages/XdmNodeItemMessage.scala b/src/main/scala/com/xmlcalabash/messages/XdmNodeItemMessage.scala index d454ac6..a0103ff 100644 --- a/src/main/scala/com/xmlcalabash/messages/XdmNodeItemMessage.scala +++ b/src/main/scala/com/xmlcalabash/messages/XdmNodeItemMessage.scala @@ -1,10 +1,12 @@ package com.xmlcalabash.messages -import com.xmlcalabash.runtime.{StaticContext, XProcMetadata} +import com.xmlcalabash.model.xxml.XStaticContext +import com.xmlcalabash.runtime.XProcMetadata +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.XdmNode class XdmNodeItemMessage(override val item: XdmNode, override val metadata: XProcMetadata, - override val context: StaticContext) + override val context: MinimalStaticContext) extends XdmValueItemMessage(item, metadata, context) { } diff --git a/src/main/scala/com/xmlcalabash/messages/XdmValueItemMessage.scala b/src/main/scala/com/xmlcalabash/messages/XdmValueItemMessage.scala index beaaceb..a3f56bf 100644 --- a/src/main/scala/com/xmlcalabash/messages/XdmValueItemMessage.scala +++ b/src/main/scala/com/xmlcalabash/messages/XdmValueItemMessage.scala @@ -1,10 +1,12 @@ package com.xmlcalabash.messages -import com.xmlcalabash.runtime.{StaticContext, XProcMetadata} +import com.xmlcalabash.model.xxml.XStaticContext +import com.xmlcalabash.runtime.XProcMetadata +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.XdmValue class XdmValueItemMessage(override val item: XdmValue, override val metadata: XProcMetadata, - override val context: StaticContext) + override val context: MinimalStaticContext) extends XProcItemMessage(item, metadata, context) { } diff --git a/src/main/scala/com/xmlcalabash/model/util/SaxonTreeBuilder.scala b/src/main/scala/com/xmlcalabash/model/util/SaxonTreeBuilder.scala index 4e27f56..aa1f7fc 100644 --- a/src/main/scala/com/xmlcalabash/model/util/SaxonTreeBuilder.scala +++ b/src/main/scala/com/xmlcalabash/model/util/SaxonTreeBuilder.scala @@ -2,7 +2,7 @@ package com.xmlcalabash.model.util import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.{ExceptionCode, ModelException, XProcException} -import com.xmlcalabash.runtime.XMLCalabashRuntime +import com.xmlcalabash.runtime.{XMLCalabashProcessor, XMLCalabashRuntime} import com.xmlcalabash.util.{DefaultLocation, S9Api, SysIdLocation, VoidLocation} import net.sf.saxon.`type`.{SchemaType, Untyped} import net.sf.saxon.event.{NamespaceReducer, Receiver} @@ -61,14 +61,10 @@ class SaxonTreeBuilder(processor: Processor) { private val emptyAttributeMap = EmptyAttributeMap.getInstance() private var _location = Option.empty[Location] - def this(config: XMLCalabash) = { + def this(config: XMLCalabashProcessor) = { this(config.processor) } - def this(runtime: XMLCalabashRuntime) = { - this(runtime.config) - } - def location: Option[Location] = _location def location_=(loc: Location): Unit = { _location = Some(loc) @@ -144,8 +140,7 @@ class SaxonTreeBuilder(processor: Processor) { throw new ModelException(ExceptionCode.BADTREENODE, List(node.getNodeKind.toString, node.getNodeName.toString), node) } case xpe: XPathException => - // FIXME: wrap in xprocexception - throw new RuntimeException(xpe) + throw xpe } } @@ -256,7 +251,6 @@ class SaxonTreeBuilder(processor: Processor) { def addStartElement(elemName: NodeName, attrs: AttributeMap, typeCode: SchemaType, nsmap: NamespaceMap): Unit = { // Sort out the namespaces... var newmap = updateMap(nsmap, elemName.getPrefix, elemName.getURI) - // FIXME: this should be iterable? for (attr <- attrs.asList.asScala) { if (attr.getNodeName.getURI != null && attr.getNodeName.getURI != "") { newmap = updateMap(newmap, attr.getNodeName.getPrefix, attr.getNodeName.getURI) @@ -276,8 +270,7 @@ class SaxonTreeBuilder(processor: Processor) { _location = None } catch { case e: XPathException => - // FIXME: some sort of XProcException - throw new RuntimeException(e) + throw e } } @@ -299,8 +292,7 @@ class SaxonTreeBuilder(processor: Processor) { return nsmap } - // FIXME: runtime exception should be some form of xproc exception? - throw new RuntimeException("Cannot add " + prefix + " to namespace map with URI " + uri) + throw XProcException.xiImpossibleNamespaceConfiguration(prefix, uri, None) } def addEndElement(): Unit = { diff --git a/src/main/scala/com/xmlcalabash/model/util/ValueParser.scala b/src/main/scala/com/xmlcalabash/model/util/ValueParser.scala index fec6bc9..ebe1066 100644 --- a/src/main/scala/com/xmlcalabash/model/util/ValueParser.scala +++ b/src/main/scala/com/xmlcalabash/model/util/ValueParser.scala @@ -5,11 +5,10 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.{ExceptionCode, ModelException, XProcException} import com.xmlcalabash.model.util.XProcConstants.ValueTemplate import com.xmlcalabash.runtime.{StaticContext, XMLCalabashRuntime, XProcExpression, XProcVtExpression, XProcXPathExpression} -import com.xmlcalabash.util.{TypeUtils, ValueTemplateParser} +import com.xmlcalabash.util.{MinimalStaticContext, TypeUtils, ValueTemplateParser} import net.sf.saxon.s9api.{Axis, ItemType, QName, XdmAtomicValue, XdmItem, XdmMap, XdmNode, XdmNodeKind, XdmValue} import scala.collection.mutable -import scala.collection.mutable.ListBuffer object ValueParser { def parseAvt(value: String): Option[ValueTemplate] = { @@ -97,7 +96,7 @@ object ValueParser { if (static) { throw XProcException.xsBadTypeValue(value.get, "boolean", location) } else { - throw XProcException.xdBadValue(value.get, "boolean", location) + throw XProcException.xdBadType(value.get, "boolean", location) } } } else { @@ -106,7 +105,7 @@ object ValueParser { } - def parseParameters(value: XdmValue, context: StaticContext): Map[QName, XdmValue] = { + def parseParameters(value: XdmValue, context: MinimalStaticContext): Map[QName, XdmValue] = { val params = mutable.HashMap.empty[QName, XdmValue] value match { @@ -117,7 +116,7 @@ object ValueParser { val key = iter.next() val value = map.get(key) - val qname = ValueParser.parseQName(key.getStringValue, context) + val qname = context.parseQName(key.getStringValue) params.put(qname, value) } case _ => @@ -127,7 +126,7 @@ object ValueParser { params.toMap } - def parseDocumentProperties(value: XdmItem, context: StaticContext, location: Option[Location]): Map[QName, XdmValue] = { + def parseDocumentProperties(value: XdmItem, context: MinimalStaticContext, location: Option[Location]): Map[QName, XdmValue] = { val params = mutable.HashMap.empty[QName, XdmValue] value match { @@ -143,7 +142,7 @@ object ValueParser { case XProcConstants.xs_QName => key.getQNameValue case XProcConstants.xs_string => - ValueParser.parseQName(key.getStringValue, context) + context.parseQName(key.getStringValue) case _ => throw XProcException.xdBadMapKey(key.getStringValue, location) } diff --git a/src/main/scala/com/xmlcalabash/model/xml/XMLViewportComposer.scala b/src/main/scala/com/xmlcalabash/model/util/XMLViewportComposer.scala similarity index 89% rename from src/main/scala/com/xmlcalabash/model/xml/XMLViewportComposer.scala rename to src/main/scala/com/xmlcalabash/model/util/XMLViewportComposer.scala index a33fd4e..af30129 100644 --- a/src/main/scala/com/xmlcalabash/model/xml/XMLViewportComposer.scala +++ b/src/main/scala/com/xmlcalabash/model/util/XMLViewportComposer.scala @@ -1,20 +1,20 @@ -package com.xmlcalabash.model.xml +package com.xmlcalabash.model.util import com.jafpl.messages.{Message, Metadata} import com.jafpl.steps.{ViewportComposer, ViewportItem} import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{AnyItemMessage, XdmNodeItemMessage, XdmValueItemMessage} -import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} -import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XProcVtExpression, XProcXPathExpression} -import com.xmlcalabash.util.{TypeUtils, VoidLocation} +import com.xmlcalabash.model.xxml.XStaticContext +import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, XProcMetadata} +import com.xmlcalabash.util.TypeUtils import net.sf.saxon.om.{AttributeMap, SingletonAttributeMap} -import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmNode, XdmValue} +import net.sf.saxon.s9api.{QName, XdmNode} import scala.collection.mutable import scala.collection.mutable.ListBuffer -class XMLViewportComposer(config: XMLCalabash, context: StaticContext, patternString: String) extends ViewportComposer { +class XMLViewportComposer(config: XMLCalabash, context: XStaticContext, matchExpr: String) extends ViewportComposer { private val cx_viewport = new QName("cx", XProcConstants.ns_cx,"viewport") private val _index = new QName("index") private var matcher: ProcessMatch = _ @@ -22,7 +22,7 @@ class XMLViewportComposer(config: XMLCalabash, context: StaticContext, patternSt private var metadata: XProcMetadata = _ private var items = ListBuffer.empty[XMLViewportItem] private var itemIndex = 0 - private var dynBindings = Map.empty[String, Message] + private var dynBindings: Map[String, Message] = _ override def runtimeBindings(bindings: Map[String, Message]): Unit = { dynBindings = bindings @@ -41,7 +41,7 @@ class XMLViewportComposer(config: XMLCalabash, context: StaticContext, patternSt throw XProcException.xiThisCantHappen("Viewport message without metadata?", context.location) } - val bindings = mutable.HashMap.empty[String,Message] ++ context.statics + val bindings = mutable.HashMap.empty[String,Message] ++ context.inscopeConstantBindings for ((name, message) <- dynBindings) { bindings.put(name,message) } @@ -54,9 +54,10 @@ class XMLViewportComposer(config: XMLCalabash, context: StaticContext, patternSt */ matcher = new ProcessMatch(config, new Decomposer(), context, bindings.toMap) - matcher.process(source, patternString) + matcher.process(source, matchExpr) decomposed = matcher.result items.toList + } override def recompose(): Message = { @@ -94,7 +95,7 @@ class XMLViewportComposer(config: XMLCalabash, context: StaticContext, patternSt } override def attributes(node: XdmNode, matchingAttributes: AttributeMap, nonMatchingAttributes: AttributeMap): Option[AttributeMap] = { - throw XProcException.xdViewportOnAttribute(patternString, context.location) + throw XProcException.xdViewportOnAttribute(matchExpr, context.location) } override def text(node: XdmNode): Unit = { @@ -148,7 +149,7 @@ class XMLViewportComposer(config: XMLCalabash, context: StaticContext, patternSt } override def attributes(node: XdmNode, matchingAttributes: AttributeMap, nonMatchingAttributes: AttributeMap): Option[AttributeMap] = { - throw XProcException.xcInvalidSelection(patternString, "attribute", None) + throw XProcException.xcInvalidSelection(matchExpr, "attribute", None) } override def text(node: XdmNode): Unit = { @@ -203,4 +204,5 @@ class XMLViewportComposer(config: XMLCalabash, context: StaticContext, patternSt } } } + } diff --git a/src/main/scala/com/xmlcalabash/model/util/XProcConstants.scala b/src/main/scala/com/xmlcalabash/model/util/XProcConstants.scala index e9d969b..92ab7cb 100644 --- a/src/main/scala/com/xmlcalabash/model/util/XProcConstants.scala +++ b/src/main/scala/com/xmlcalabash/model/util/XProcConstants.scala @@ -1,6 +1,7 @@ package com.xmlcalabash.model.util import net.sf.saxon.lib.SaxonOutputKeys +import net.sf.saxon.om.StructuredQName import net.sf.saxon.s9api.QName import net.sf.saxon.serialize.SerializationProperties @@ -26,6 +27,8 @@ object XProcConstants { val UNKNOWN = new QName("", "UNKNOWN") + val structured_xs_QName = new StructuredQName("xs", XProcConstants.ns_xs, "QName") + val xml_base = new QName("xml", ns_xml, "base") val xml_id = new QName("xml", ns_xml, "id") val xml_lang = new QName("xml", ns_xml, "lang") @@ -34,6 +37,16 @@ object XProcConstants { val fn_collection = new QName("fn", ns_fn, "collection") val fn_map = new QName("fn", ns_fn, "map") + val fn_position = new QName("fn", ns_fn, "position") + val fn_last = new QName("fn", ns_fn, "last") + val fn_current_dateTime = new QName("fn", ns_fn, "current-dateTime") + val fn_current_date = new QName("fn", ns_fn, "current-date") + val fn_current_time = new QName("fn", ns_fn, "current-time") + val fn_implicit_timezone = new QName("fn", ns_fn, "implicit-timezone") + val fn_default_collation = new QName("fn", ns_fn, "default-collation") + val fn_default_language = new QName("fn", ns_fn, "default-language") + val fn_static_base_uri = new QName("fn", ns_fn, "static-base-uri") + val p_catch = new QName("p", ns_p, "catch") val p_choose = new QName("p", ns_p, "choose") val p_data = new QName("p", ns_p, "data") @@ -70,6 +83,7 @@ object XProcConstants { val p_serialization = new QName("p", ns_p, "serialization") val p_sink = new QName("p", ns_p, "sink") val p_start = new QName("p", ns_p, "start") + val p_system_property = new QName("p", ns_p, "system-property") val p_try = new QName("p", ns_p, "try") val p_variable = new QName("p", ns_p, "variable") val p_viewport = new QName("p", ns_p, "viewport") @@ -84,12 +98,14 @@ object XProcConstants { val cx_creation_time = new QName("cx", ns_cx, "creation-time") val cx_document = new QName("cx", ns_cx, "document") val cx_document_loader = new QName("cx", ns_cx, "document-loader") + val cx_document_loader_vt = new QName("cx", ns_cx, "document-loader-vt") val cx_dtd_validate = new QName("cx", ns_cx,"dtd-validate") val cx_empty_loader = new QName("cx", ns_cx, "empty-loader") val cx_encoding = new QName("cx", ns_cx, "encoding") val cx_exception_translator = new QName("cx", ns_cx, "exception-translator") val cx_filter = new QName("cx", ns_cx, "filter") val cx_inline_loader = new QName("cx", ns_cx, "inline-loader") + val cx_inline_loader_vt = new QName("cx", ns_cx, "inline-loader-vt") val cx_last_accessed = new QName("cx", ns_cx, "last-accessed") val cx_loop = new QName("cx", ns_cx, "loop") val cx_multipart = new QName("cx", ns_cx, "multipart") @@ -98,6 +114,7 @@ object XProcConstants { val cx_unknown = new QName("cx", ns_cx, "unknown") val cx_until = new QName("cx", ns_cx, "until") val cx_use_default_input = new QName("cx", ns_cx, "use-default-input") + val cx_value_computation = new QName("cx", ns_cx, "value-computation") val cx_while = new QName("cx", ns_cx, "while") val cc_debug_graph = new QName("cc", ns_cc, "debug-graph") @@ -290,11 +307,13 @@ object XProcConstants { val _suffix = new QName("", "suffix") val _suppress_cookies = new QName("", "suppress-cookies") val _suppress_indentation = new QName("", "suppress-indentation") + val _synthetic = new QName("", "synthetic") val _target = new QName("", "target") val _test = new QName("", "test") val _timeout = new QName("", "timeout") val _timestamp = new QName("", "timestamp") val _transfer_encoding = new QName("", "transfer-encoding") + val _tumble_id = new QName("", "tumble-id") val _type = new QName("", "type") val _undeclare_prefixes = new QName("", "undeclare-prefixes") val _use_when = new QName("", "use-when") @@ -335,11 +354,12 @@ object XProcConstants { val p_expand_text = new QName("p", ns_p, "expand-text") val p_inline_expand_text = new QName("p", ns_p, "inline-expand-text") val p_message = new QName("p", ns_p, "message") + val p_timeout = new QName("p", ns_p, "timeout") val p_use_when = new QName("p", ns_p, "use-when") val err_XC0095 = new QName(ns_err, "XC0095") val err_XTMM9000 = new QName(ns_xqt_errors, "XTMM9000") val err_XTDE0040 = new QName(ns_xqt_errors, "XTDE0040") - val BUILD_TREE = ValueParser.parseClarkName(SaxonOutputKeys.BUILD_TREE) + val BUILD_TREE: QName = ValueParser.parseClarkName(SaxonOutputKeys.BUILD_TREE) } diff --git a/src/main/scala/com/xmlcalabash/model/util/XValueParser.scala b/src/main/scala/com/xmlcalabash/model/util/XValueParser.scala new file mode 100644 index 0000000..4b96d21 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/util/XValueParser.scala @@ -0,0 +1,130 @@ +package com.xmlcalabash.model.util + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants.ValueTemplate +import com.xmlcalabash.util.{MinimalStaticContext, ValueTemplateParser} +import net.sf.saxon.expr.parser.ExpressionTool +import net.sf.saxon.s9api.{QName, SaxonApiException} + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +object XValueParser { + private val contextFunctions = + List(XProcConstants.p_iteration_size, XProcConstants.p_iteration_position, XProcConstants.fn_collection) + private val nonStaticFunctions = + List(XProcConstants.fn_current_dateTime, XProcConstants.fn_current_date, + XProcConstants.fn_current_time, XProcConstants.fn_implicit_timezone, + XProcConstants.fn_default_collation, XProcConstants.fn_default_language, + XProcConstants.fn_static_base_uri, XProcConstants.p_system_property + // I don't think position() and last() are non-static from XProc's perspective... + ) + + def parseAvt(value: String): ValueTemplate = { + val parser = new ValueTemplateParser(value) + parser.template() + } +} + +class XValueParser private(config: XMLCalabash, context: MinimalStaticContext) { + private var _contextDependent = false + private var _static = true + private val _variableRefs = mutable.HashSet.empty[QName] + private val _functionRefs = mutable.HashSet.empty[QName] + private val _expressions = ListBuffer.empty[String] + private val _segments = ListBuffer.empty[ExpressionParser] + + def contextDependent: Boolean = _contextDependent + def static: Boolean = _static && !_contextDependent + def variables: Set[QName] = _variableRefs.toSet + def functions: Set[QName] = _functionRefs.toSet + + def this(config: XMLCalabash, context: MinimalStaticContext, avt: ValueTemplate) = { + this(config, context) + var inexpr = false + for (substr <- avt) { + if (inexpr) { + _expressions += substr + + val parser = config.expressionParser + parser.parse(substr) + _segments += parser + } + inexpr = !inexpr + } + + findVariableRefs() + findFunctionRefs() + dependsOnContext() + } + + def this(config: XMLCalabash, context: MinimalStaticContext, select: String) = { + this(config, context) + _expressions += select + + val parser = config.expressionParser + parser.parse(select) + _segments += parser + + findVariableRefs() + findFunctionRefs() + dependsOnContext() + } + + private def findVariableRefs(): Unit = { + for (segment <- _segments) { + for (ref <- segment.variableRefs) { + _variableRefs += context.parseClarkName(ref) + } + } + } + + private def findFunctionRefs(): Unit = { + for (segment <- _segments) { + for (ref <- segment.functionRefs) { + val qname = context.parseQName(ref) + if (Option(qname.getNamespaceURI).isEmpty || qname.getNamespaceURI == "") { + _functionRefs += new QName("fn", XProcConstants.ns_fn, qname.getLocalName) + } else { + _functionRefs += qname + } + } + } + } + + private def dependsOnContext(): Unit = { + // Make sure functions and variables have been computed first! + for (func <- _functionRefs) { + _contextDependent = _contextDependent || XValueParser.contextFunctions.contains(func) + if (XValueParser.nonStaticFunctions.contains(func)) { + _static = false + } + } + + if (!_contextDependent) { + for (expr <- _expressions) { + val xcomp = config.processor.newXPathCompiler() + for ((prefix, uri) <- context.inscopeNamespaces) { + xcomp.declareNamespace(prefix, uri) + } + for (name <- _variableRefs) { + xcomp.declareVariable(name) + } + try { + val xexec = xcomp.compile(expr) + val xexpr = xexec.getUnderlyingExpression.getInternalExpression + _contextDependent = _contextDependent || ExpressionTool.dependsOnFocus(xexpr) + } catch { + /* + case ex: SaxonApiException => + throw XProcException.xsStaticErrorInExpression(expr, ex.getMessage, None) + case ex: Exception => + throw ex + */ + case ex: Exception => _static = false + } + } + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Artifact.scala b/src/main/scala/com/xmlcalabash/model/xml/Artifact.scala deleted file mode 100644 index 970e032..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Artifact.scala +++ /dev/null @@ -1,503 +0,0 @@ -package com.xmlcalabash.model.xml - -import java.net.URI -import com.jafpl.graph.{Location, Node} -import com.jafpl.messages.Message -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.config.StepSignature -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.messages.{XdmNodeItemMessage, XdmValueItemMessage} -import com.xmlcalabash.model.util.{UniqueId, ValueParser, XProcConstants} -import com.xmlcalabash.runtime.params.{StepParams, XPathBindingParams} -import com.xmlcalabash.runtime.{StaticContext, XMLCalabashRuntime, XProcLocation, XProcXPathExpression} -import com.xmlcalabash.util.S9Api -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{Axis, QName, SaxonApiException, XdmNode, XdmNodeKind, XdmValue} -import org.slf4j.{Logger, LoggerFactory} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer -import scala.reflect.ClassTag - -class Artifact(val config: XMLCalabash) { - protected val logger: Logger = LoggerFactory.getLogger(this.getClass) - protected[model] var _readablePorts = List.empty[Port] - protected[model] var _graphNode: Option[Node] = None - private var _parent: Option[Artifact] = None - private val _children: ListBuffer[Artifact] = ListBuffer.empty[Artifact] - protected[model] var _staticContext: XMLContext = new XMLContext(config, this) - private var _xmlId = Option.empty[String] - protected var _synthetic = true - private val _uid = UniqueId.nextId - private var _tumbleId = s"!syn_$uid" - private var _expand_text = Option.empty[Boolean] - protected[model] var _inScopeStatics = mutable.HashMap.empty[String, NameBinding] - protected[model] var _inScopeDynamics = mutable.HashMap.empty[QName, NameBinding] - - protected[model] val attributes = mutable.HashMap.empty[QName, String] - protected[model] val extensionAttributes = mutable.HashMap.empty[QName, String] - - def graphNode: Option[Node] = _graphNode - - protected[model] def expand_text: Boolean = { - if (_expand_text.isDefined) { - _expand_text.get - } else { - if (parent.isDefined) { - parent.get.expand_text - } else { - true - } - } - } - - protected[model] def attr(name: QName): Option[String] = { - if (attributes.contains(name)) { - val str = attributes(name) - attributes.remove(name) - Some(str) - } else { - None - } - } - protected[xmlcalabash] def extensionAttr(name: QName): Option[String] = { - extensionAttributes.get(name) - } - - protected[xmlcalabash] def parent: Option[Artifact] = _parent - protected[model] def parent_=(art: Artifact): Unit = { - _parent = Some(art) - } - protected[model] def rawChildren: List[Artifact] = _children.toList - - protected[model] def allChildren: List[Artifact] = { - _children.toList.flatMap { - case _: Documentation => None - case _: PipeInfo => None - case art:Artifact => Some(art) - } - } - - protected[model] def children[T <: Artifact](implicit tag: ClassTag[T]): List[T] = { - allChildren.flatMap { - case art: T => Some(art) - case _ => None - } - } - - protected[model] def findAll[T <: Artifact](implicit tag: ClassTag[T]): List[T] = { - root.findDescendants(tag) - } - - protected[model] def findDescendants[T <: Artifact](implicit tag: ClassTag[T]): List[T] = { - val arts = ListBuffer.empty[T] - for (child <- allChildren) { - child match { - case t: T => - arts += t - arts ++= t.findDescendants(tag) - case _ => - arts ++= child.findDescendants(tag) - } - } - arts.toList - } - - protected[model] def root: Artifact = { - var root: Artifact = this - while (root.parent.isDefined) { - root = root.parent.get - } - root - } - - protected[model] def findInScopeOption(name: QName): Option[NameBinding] = { - findInScopeOption(this, name, this) - } - - protected[model] def findInScopeOption(art: Artifact, name: QName, self: Artifact): Option[NameBinding] = { - var found = Option.empty[NameBinding] - - art match { - case _: AtomicStep => - () // No one can see the options of atomic steps - case _ => - for (nb <- art.children[NameBinding]) { - if ((nb ne self) && nb.name == name) { - found = Some(nb) - } - } - } - - if (found.isEmpty && art.parent.isDefined) { - findInScopeOption(art.parent.get, name, self) - } else { - found - } - } - - protected[model] def ancestor(art: Artifact): Boolean = { - var p: Option[Artifact] = parent - while (p.isDefined) { - if (p.get == art) { - return true - } - p = p.get.parent - } - false - } - - protected[model] def containingStep: Option[Step] = { - var p: Option[Artifact] = Some(this) - while (p.isDefined) { - p.get match { - case step: Step => - return Some(step) - case _ => - p = p.get.parent - } - } - None - } - - protected[xmlcalabash] def location: Option[Location] = _staticContext.location - protected[model] def staticContext: XMLContext = _staticContext - protected[model] def staticContext_=(context: StaticContext): Unit = { - _staticContext = new XMLContext(config, this, context.baseURI, context.nsBindings, context.location) - } - protected[model] def uid: Long = _uid - protected[model] def xml_id: Option[String] = _xmlId - protected[model] def tumble_id: String = _tumbleId - protected[model] def synthetic: Boolean = _synthetic - - protected[model] def inScopeStatics: Map[String, Message] = { - val statics = mutable.HashMap.empty[String,Message] - for ((name,binding) <- _inScopeStatics) { - if (binding.staticValue.isDefined) { - statics.put(name, binding.staticValue.get) - } - } - statics.toMap - } - - protected[model] def addChild(art: Artifact): Unit = { - _children += art - art.parent = this - } - - protected[model] def addChild(art: Artifact, before: Option[Artifact]): Unit = { - if (before.isDefined) { - addChild(art, before.get) - } else { - addChild(art) - } - } - - protected[model] def addChild(art: Artifact, before: Artifact): Unit = { - var pos = 0 - var found = false - for (child <- allChildren) { - found = found || child == before - if (!found) { - pos += 1 - } - } - if (!found) { - throw new RuntimeException("Insert before is not a child of the container") - } - - _children.insert(pos, art) - art.parent = this - } - - protected[model] def replaceChild(art: Artifact, target: Artifact): Unit = { - var pos = 0 - var found = false - for (child <- rawChildren) { - found = found || child == target - if (!found) { - pos += 1 - } - } - if (!found) { - throw new RuntimeException("Replace target is not a child of the container") - } - - _children.insert(pos, art) - _children.remove(pos+1) - art.parent = this - } - - protected[model] def removeChild(target: Artifact): Unit = { - var pos = 0 - var childPos = -1 - for (child <- _children) { - if (child == target) { - childPos = pos - } - pos += 1 - } - if (childPos >= 0) { - _children.remove(childPos) - } - } - - protected[model] def removeChildren(): Unit = { - _children.clear() - } - - protected[model] def firstChild: Option[Artifact] = allChildren.headOption - - protected[model] def firstWithInput: Option[WithInput] = { - children[WithInput].headOption - } - - protected[model] def firstStepChild: Option[Step] = { - children[Step].headOption - } - - private def tumbleId(node: XdmNode): String = { - var count = 1 - val iter = node.axisIterator(Axis.PRECEDING_SIBLING) - while (iter.hasNext) { - val child = iter.next() - if (child.getNodeKind == XdmNodeKind.ELEMENT) { - count += 1 - } - } - val parent = Option(node.getParent) - if (parent.isEmpty) { - "!" - } else { - val pid = tumbleId(parent.get) - if (pid == "!") { - s"$pid$count" - } else { - s"$pid.$count" - } - } - } - - def inScopeExpandText(node: XdmNode): Boolean = { - if (node.getNodeKind == XdmNodeKind.ELEMENT) { - val attr = if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { - XProcConstants._expand_text - } else { - XProcConstants.p_expand_text - } - val expand = Option(node.getAttributeValue(attr)) - if (expand.isDefined) { - // FIXME: what about AVT? what about invalid values? - return expand.get == "true" - } - } - - if (Option(node.getParent).isDefined) { - inScopeExpandText(node.getParent) - } else { - false - } - } - - protected[model] def parse(node: XdmNode): Unit = { - _synthetic = false - _tumbleId = tumbleId(node) - _staticContext.baseURI = node.getBaseURI - _staticContext.location = new XProcLocation(node) - _staticContext.nsBindings = S9Api.inScopeNamespaces(node) - - // Parse attributes - val aiter = node.axisIterator(Axis.ATTRIBUTE) - while (aiter.hasNext) { - val attr = aiter.next() - attr.getNodeName match { - case XProcConstants.xml_base => staticContext.baseURI = new URI(attr.getStringValue) - case XProcConstants.xml_id => _xmlId = Some(attr.getStringValue) - case _ => - if (attr.getNodeName.getNamespaceURI == "" || attr.getNodeName.getNamespaceURI == XProcConstants.ns_p) { - attributes.put(attr.getNodeName, attr.getStringValue) - } else { - extensionAttributes.put(attr.getNodeName, attr.getStringValue) - } - } - } - - val ns = S9Api.inScopeNamespaces(node) - var same = parent.isDefined - if (parent.isDefined) { - val inScopeNS = parent.get.staticContext.nsBindings - for (prefix <- inScopeNS.keySet) { - same = same && (ns.contains(prefix) && (ns(prefix) == inScopeNS(prefix))) - } - for (prefix <- ns.keySet) { - same = same && (inScopeNS.contains(prefix) && (ns(prefix) == inScopeNS(prefix))) - } - } - - if (same) { - staticContext.nsBindings = parent.get.staticContext.nsBindings - } else { - staticContext.nsBindings = ns - } - - if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { - _expand_text = staticContext.parseBoolean(attr(XProcConstants._expand_text)) - } else { - _expand_text = staticContext.parseBoolean(attr(XProcConstants.p_expand_text)) - } - } - - - protected[model] def declaration(stepType: QName): Option[StepSignature] = { - // First find the decl container - var container: Option[Artifact] = Some(this) - while (container.isDefined) { - container.get match { - case decl: DeclContainer => - return declaration(stepType, decl) - case _ => - container = container.get.parent - } - } - None - } - - protected[model] def declaration(stepType: QName, container: DeclContainer): Option[StepSignature] = { - if (parent.isDefined) { - parent.get.declaration(stepType, container) - } else { - None - } - } - - protected[model] def environment(): Environment = { - // N.B. Do not cache this; the structure changes when filters are added. - Environment.newEnvironment(this) - } - - protected[model] def makeStructureExplicit(): Unit = { - for (child <- allChildren) { - child.makeStructureExplicit() - } - } - - protected[model] def validateStructure(): Unit = { - for (child <- allChildren) { - child.validateStructure() - } - } - - protected[model] def makeBindingsExplicit(): Unit = { - val env = environment() - - for (sbinding <- env.staticVariables) { - _inScopeStatics.put(sbinding.name.getClarkName, sbinding) - } - - for (dbinding <- env.variables) { - _inScopeDynamics.put(dbinding.name, dbinding) - } - - for (child <- allChildren) { - child.makeBindingsExplicit() - } - } - - protected[model] def normalizeToPipes(): Unit = { - for (child <- allChildren) { - child.normalizeToPipes() - } - } - - protected[model] def addFilters(): Unit = { - for (child <- allChildren) { - child.addFilters() - } - } - - protected[model] def insertPipe(source: Port, pipe: Pipe): Unit = { - for (child <- allChildren) { - child.insertPipe(source, pipe) - } - } - - protected[model] def replumb(oldSource: Port, newSource: Port): Unit = { - for (child <- allChildren) { - child.replumb(oldSource, newSource) - } - } - - protected[model] def xpathBindingParams(): XPathBindingParams = { - val statics = mutable.HashMap.empty[QName, XdmValue] - for ((name,binding) <- _inScopeStatics) { - val qname = ValueParser.parseClarkName(name) - val smsg = binding.staticValue.get - smsg match { - case msg: XdmNodeItemMessage => - statics.put(qname, msg.item) - case msg: XdmValueItemMessage => - statics.put(qname, msg.item) - case _ => - throw new RuntimeException("Unexpected message type") - } - } - statics.toMap - new XPathBindingParams(statics.toMap) - } - - protected[model] def stepParams(): StepParams = { - new StepParams(inScopeStatics) - } - - def computeStatically(xpathExpression: String): XdmValueItemMessage = { - // Evaluate it; no reference to context or non-statics is allowed. - val exprContext = staticContext.withStatics(inScopeStatics) - val expr = new XProcXPathExpression(staticContext, xpathExpression) - try { - config.expressionEvaluator.newInstance().value(expr, List(), exprContext.statics, None) - } catch { - case sae: SaxonApiException => - throw XProcException.xsStaticErrorInExpression(xpathExpression, sae.getMessage, exprContext.location) - } - } - - def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - if (allChildren.nonEmpty) { - if (_graphNode.isDefined) { - for (child <- allChildren) { - child.graphNodes(runtime, _graphNode.get) - } - } else { - println("cannot graphNodes for children of " + this) - } - } - } - - def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - println(s"ERROR: $this doesn't override graphEdges") - } - - def dump(): Unit = { - dump("", Set.empty[Artifact]) - } - - private def dump(indent: String, dumped: Set[Artifact]): Unit = { - println(s"$indent$this") - for (child <- allChildren) { - child match { - case _: Step => - println("") - case _ => () - } - if (dumped.contains(child)) { - println(s"$indent$this...") - } else { - child.dump(s"$indent ", dumped + child) - } - } - } - - def xdump(xml: ElaboratedPipeline): Unit = { - throw new RuntimeException(s"Don't know how to xdump $this") - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/AtomicLoader.scala b/src/main/scala/com/xmlcalabash/model/xml/AtomicLoader.scala deleted file mode 100644 index c2e495a..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/AtomicLoader.scala +++ /dev/null @@ -1,27 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.runtime.{ImplParams, XMLCalabashRuntime} - -class AtomicLoader(override val config: XMLCalabash, params: Option[ImplParams]) extends AtomicStep(config, params) { - - def this(config: XMLCalabash, params: ImplParams, context: Artifact) = { - this(config, Some(params)) - _inScopeStatics = context._inScopeStatics - _inScopeDynamics = context._inScopeDynamics - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - for ((name, np) <- _inScopeDynamics) { - val binding = findInScopeOption(name) - if (binding.isDefined) { - // Can be undefined if it's on an atomic step - val pipe = new NamePipe(config, name, this.tumble_id, binding.get) - pipe.graphEdges(runtime, _graphNode.get) - } - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/AtomicStep.scala b/src/main/scala/com/xmlcalabash/model/xml/AtomicStep.scala deleted file mode 100644 index a6d27fd..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/AtomicStep.scala +++ /dev/null @@ -1,368 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node} -import com.jafpl.messages.Message -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.{ExceptionCode, ModelException, XProcException} -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.params.StepParams -import com.xmlcalabash.runtime.{ImplParams, StepExecutable, StepProxy, StepRunner, StepWrapper, XMLCalabashRuntime, XmlStep} -import com.xmlcalabash.steps.internal.{DocumentLoader, InlineLoader} -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.ma.arrays.ArrayItemType -import net.sf.saxon.ma.map.MapType -import net.sf.saxon.s9api.{QName, XdmNode} - -import java.lang.reflect.InvocationTargetException -import scala.collection.mutable - -class AtomicStep(override val config: XMLCalabash, params: Option[ImplParams]) extends Step(config) with NamedArtifact { - def this(config: XMLCalabash) = { - this(config, None) - } - - def this(config: XMLCalabash, params: ImplParams) = { - this(config, Some(params)) - } - - def this(config: XMLCalabash, params: ImplParams, context: Artifact) = { - this(config, Some(params)) - _inScopeStatics = context._inScopeStatics - _inScopeDynamics = context._inScopeDynamics - } - - private var _stepType: QName = _ - private var _stepImplementation: StepExecutable = _ - - def stepType: QName = _stepType - protected[model] def stepType_=(stype: QName): Unit = { - _stepType = stype - } - - override def parse(node: XdmNode): Unit = { - super.parse(node) - _stepType = node.getNodeName - } - - override protected[model] def makeStructureExplicit(): Unit = { - if (declaration(stepType).isEmpty) { - throw XProcException.xsMissingDeclaration(stepType, location) - } - - val decl = declaration(stepType).get - - for (item <- allChildren) { - item match { - case winput: WithInput => - winput.makeStructureExplicit() - if (winput.port == "") { - if (decl.primaryInput.isDefined) { - winput.port = decl.primaryInput.get.port - } - } - case _ => - item.makeStructureExplicit() - } - } - - // Make sure there are with-{input/output/option} elements - for (dinput <- decl.inputs) { - var found = Option.empty[WithInput] - for (winput <- children[WithInput]) { - if (dinput.port == winput.port) { - found = Some(winput) - } - } - if (found.isEmpty) { - val newwi = new WithInput(config) - newwi.port = dinput.port - newwi.primary = dinput.primary - newwi.sequence = dinput.sequence - addChild(newwi) - } - } - - for (doutput <- decl.outputs) { - val newwo = new WithOutput(config) - newwo.port = doutput.port - newwo.primary = doutput.primary - newwo.sequence = doutput.sequence - addChild(newwo) - } - - for (doption <- decl.options) { - var found = Option.empty[WithOption] - for (woption <- children[WithOption]) { - if (woption.name == doption.name) { - found = Some(woption) - } - } - - if (found.isEmpty) { - if (attributes.contains(doption.name)) { - val woption = new WithOption(config, doption.name) - woption.staticContext = staticContext - - if (doption.declaredType.isDefined) { - val dtype = doption.declaredType.get - woption.as = dtype - woption.qnameKeys = doption.forceQNameKeys - - dtype.getItemType.getUnderlyingItemType match { - case _: MapType => - woption.select = attr(doption.name).get - case _: ArrayItemType => - woption.select = attr(doption.name).get - case _ => - woption.avt = attr(doption.name).get - } - } else { - woption.avt = attr(doption.name).get - } - - found = Some(woption) - addChild(woption) - } else { - if (doption.required) { - throw XProcException.xsMissingRequiredOption(doption.name, location) - } else { - val woption = new WithOption(config, doption.name) - woption.staticContext = staticContext - woption.select = doption.defaultSelect.getOrElse("()") - found = Some(woption) - addChild(woption) - } - } - } else { - if (attributes.contains(doption.name)) { - throw XProcException.xsDupWithOptionName(doption.name, location) - } - } - - // Work out any preceding options that might not be defined - var prec = true - for (precopt <- decl.options) { - prec = prec && precopt.name != found.get.name - if (prec) { - found.get.precedingOption(precopt) - } - } - - if (doption.declaredType.isDefined) { - found.get.declaredType = doption.declaredType.get - } - } - - // Make sure there are no extra options - val seenOption = mutable.HashSet.empty[QName] - for (woption <- children[WithOption]) { - if (!decl.optionNames.contains(woption.name)) { - throw XProcException.xsUndeclaredOption(stepType, woption.name, location) - } - if (seenOption.contains(woption.name)) { - throw XProcException.xsDupWithOptionName(woption.name, location) - } - seenOption += woption.name - } - - // Now manufacture "with-option"s for extension attributes - for (attr <- extensionAttributes.keySet) { - val woption = new WithOption(config, attr) - woption.staticContext = staticContext - woption.qnameKeys = false - woption.avt = extensionAttributes(attr) - - if (seenOption.contains(woption.name)) { - throw XProcException.xsDupWithOptionName(woption.name, location) - } - seenOption += woption.name - - addChild(woption) - } - - // Now make sure there are no extra inputs - val seenInput = mutable.HashSet.empty[String] - for (winput <- children[WithInput]) { - if (decl.inputPorts.isEmpty && winput.port == "") { - throw XProcException.xsNoPrimaryInputPort(stepType, location) - } - if (!decl.inputPorts.contains(winput.port)) { - throw XProcException.xsBadPortName(stepType, winput.port, location) - } - if (seenInput.contains(winput.port)) { - throw XProcException.xsDupWithInputPort(winput.port, location) - } - seenInput += winput.port - } - - // Now that we've checked the options, we can add an extra one. - // If @p:message is given, treat it like a with-option (even though - // it cannot be specified in that form). - var msgName = if (stepType.getNamespaceURI == XProcConstants.ns_p) { - XProcConstants._message - } else { - XProcConstants.p_message - } - - if (attributes.contains(msgName)) { - val woption = new WithOption(config, msgName) - woption.staticContext = staticContext - woption.avt = attr(msgName).get - addChild(woption) - } - - if (attributes.nonEmpty) { - // On an atomic step, any left over attributes are presumably attempts - // to use shortcuts for options that don't exist. - throw XProcException.xsUndeclaredOption(stepType, attributes.keySet.head, location) - } - } - - override protected[model] def validateStructure(): Unit = { - val iport = mutable.HashSet.empty[String] - - for (child <- allChildren) { - child.validateStructure() - child match { - case art: WithInput => - if (iport.contains(art.port)) { - throw XProcException.xsDupWithInputPort(art.port, location) - } - iport += art.port - case _: WithOutput => () - case _: WithOption => () - case _ => - throw new RuntimeException(s"Invalid content in atomic $this") - } - } - } - - def stepImplementation(staticContext: XMLContext): StepExecutable = { - stepImplementation(staticContext, None) - } - - def stepImplementation(staticContext: XMLContext, implParams: Option[ImplParams]): StepExecutable = { - val location = staticContext.location - - val sig = declaration(stepType).get - val implClass = sig.implementation - if (implClass.isEmpty) { - val declStep = sig.declaration - if (declStep.isDefined) { - new StepRunner(config, declStep.get, sig) - } else { - throw XProcException.xiStepImplementationError(s"No implementation for ${stepType.toString}", location) - } - } else { - try { - val klass = Class.forName(implClass.head).getDeclaredConstructor().newInstance() - klass match { - case step: XmlStep => - new StepWrapper(step, sig) - case _ => - throw XProcException.xiStepImplementationError(s"Class does not implement an XmlStep: ${stepType.toString}", location) - } - } catch { - case ex: XProcException => - throw ex - case _: ClassNotFoundException => - throw XProcException.xiStepImplementationError(s"Class not found: ${implClass.head}", location) - case _: IllegalAccessException => - throw XProcException.xiStepImplementationError(s"Constructor inaccessible: ${implClass.head}", location) - case ex: InstantiationException => - throw XProcException.xiStepImplementationError(s"Cannot instantiate ${implClass.head}: ${ex.getMessage}", location) - case ex: InvocationTargetException => - throw XProcException.xiStepImplementationError(s"Constructor threw exception ${implClass.head}: ${ex.getMessage}", location) - case ex: ExceptionInInitializerError => - throw XProcException.xiStepImplementationError(s"Constructor failed for ${implClass.head}: ${ex.getMessage}", location) - case ex: Throwable => - throw XProcException.xiStepImplementationError(s"Failed to instantiate ${implClass.head}: ${ex.getMessage}", location) - } - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[ContainerStart] - _stepImplementation = stepImplementation(staticContext) - _stepImplementation.configure(config, stepType, _name, params) - - _stepImplementation match { - // If we're running an instance of a pipeline, work out which - // ports actually received inputs (even p:empty) so that - // we can tell the runtime for the pipeline. We need to do this - // so that p:empty isn't misinterpreted as an unbound port. - // Unbound ports will get default inputs if they're provided. - case runner: StepRunner => - val usedPorts = mutable.HashSet.empty[String] - for (input <- children[WithInput]) { - val bound = input.children[DataSource].nonEmpty - if (bound) { - usedPorts += input.port - } - } - runner.usedPorts(usedPorts.toSet) - case _ => () - } - - var proxyParams: Option[ImplParams] = params - - // Handle any with-option values for this step that have been computed statically - val staticallyComputed = mutable.HashMap.empty[String, Message] - for (woption <- children[WithOption]) { - if (woption.staticValue.isDefined) { - staticallyComputed.put(woption.name.getClarkName, woption.staticValue.get) - } - } - if (staticallyComputed.nonEmpty) { - if (proxyParams.isDefined) { - throw new RuntimeException("static options and constructed params?") - } - proxyParams = Some(new StepParams(staticallyComputed.toMap)) - } - - val pcontext = staticContext.withStatics(inScopeStatics) - val proxy = new StepProxy(runtime, stepType, _stepImplementation, proxyParams, pcontext) - val node = start.addAtomic(proxy, s"$stepType $stepName") - _graphNode = Some(node) - - for (child <- children[WithOption]) { - child.graphNodes(runtime, _graphNode.get) - } - - // If there are any with-options that have name-bindings to preceding - // options, patch those bindings to refer to the preceding with-options - // See test ab-option-050 - val declOptions = mutable.HashMap.empty[QName, Node] - for (child <- children[WithOption]) { - if (!child.static) { - if (child._graphNode.isDefined) { - declOptions.put(child.name, child._graphNode.get) - } - } - } - for (nb <- findDescendants[NamePipe]) { - if (declOptions.contains(nb.link.name)) { - nb.patchNode(declOptions(nb.link.name)) - } - } - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - for (child <- allChildren) { - child.graphEdges(runtime, _graphNode.get) - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startAtomic(tumble_id, stepName, stepType) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endAtomic() - } - - override def toString: String = { - s"$stepType $stepName" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Catch.scala b/src/main/scala/com/xmlcalabash/model/xml/Catch.scala deleted file mode 100644 index c734ab8..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Catch.scala +++ /dev/null @@ -1,98 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{Node, TryCatchStart} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmNode} - -import scala.collection.mutable - -class Catch(override val config: XMLCalabash) extends Container(config) with NamedArtifact { - private val _codes = mutable.HashSet.empty[QName] - - def codes: Set[QName] = _codes.toSet - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(XProcConstants._code)) { - val str = attr(XProcConstants._code).get - var repeated = Option.empty[QName] - for (code <- str.split("\\s+")) { - try { - val qname = staticContext.parseQName(code) - if (repeated.isEmpty && _codes.contains(qname)) { - repeated = Some(qname) - } - _codes += qname - } catch { - case _: Exception => - throw XProcException.xsCatchInvalidCode(code, location) - } - } - if (repeated.isDefined) { - throw XProcException.xsCatchRepeatedCode(repeated.get, location) - } - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - val input = new DeclareInput(config) - input.port = "error" - input.sequence = true - input.primary = true - addChild(input, firstChild) - - makeContainerStructureExplicit() - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[TryCatchStart] - val node = start.addCatch(stepName, codes.toList, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - for (child <- allChildren) { - child.graphEdges(runtime, _graphNode.get) - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - if (_codes.nonEmpty) { - var codes = "" - for (code <- _codes) { - if (codes != "") { - codes += ", " - } - codes += code.toString - } - - xml.startCatch(tumble_id, stepName, Some(codes)) - } else { - xml.startCatch(tumble_id, stepName, None) - } - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endCatch() - } - - override def toString: String = { - if (codes.isEmpty) { - s"p:catch $stepName" - } else { - val codestr = codes.mkString(" ") - s"p:catch $codestr $stepName" - } - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/Choose.scala b/src/main/scala/com/xmlcalabash/model/xml/Choose.scala deleted file mode 100644 index 2159a8b..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Choose.scala +++ /dev/null @@ -1,211 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ChooseStart, ContainerStart, Node, WhenStart} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.{XMLCalabashRuntime, XProcXPathExpression} -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.XdmNode - -import scala.collection.mutable - -class Choose(override val config: XMLCalabash) extends Container(config) { - private var hasWhen = false - private var hasOtherwise = false - protected var ifexpr: Option[String] = None - protected var ifcoll: Option[String] = None - protected[xml] var p_if = false - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (node.getNodeName == XProcConstants.p_if) { - if (attributes.contains(XProcConstants._test)) { - ifexpr = attr(XProcConstants._test) - } else { - throw XProcException.xsMissingRequiredAttribute(XProcConstants._test, location) - } - if (attributes.contains(XProcConstants._collection)) { - ifcoll = attr(XProcConstants._collection) - } - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - var firstChild = Option.empty[Artifact] - var input = Option.empty[WithInput] - for (child <- allChildren) { - if (firstChild.isEmpty) { - firstChild = Some(child) - } - child match { - case winput: WithInput => - if (input.isDefined) { - throw new RuntimeException("Only one with-input is allowed") - } - input = Some(winput) - winput.makeStructureExplicit() - case when: When => - hasWhen = true - when.makeStructureExplicit() - case otherwise: Otherwise => - hasOtherwise = true - otherwise.makeStructureExplicit() - } - } - - if (!hasWhen && !hasOtherwise) { - throw XProcException.xsMissingWhen(location) - } - - if (input.isEmpty) { - val winput = new WithInput(config) - winput.port = "source" - winput.primary = true - if (firstChild.isDefined) { - addChild(winput, firstChild.get) - } else { - addChild(winput) - } - } - - val outputSet = mutable.HashSet.empty[String] - var primaryOutput = Option.empty[String] - - for (branch <- children[Container]) { - for (child <- branch.children[DeclareOutput]) { - outputSet += child.port - if (child.primary) { - primaryOutput = Some(child.port) - } - } - } - - val first = firstChild - for (port <- outputSet) { - val woutput = new WithOutput(config) - woutput.port = port - woutput.primary = (primaryOutput.isDefined && primaryOutput.get == port) - addChild(woutput, first) - } - - if (!hasOtherwise) { - val other = new Otherwise(config) - other.test = "true()" - - val identity = new AtomicStep(config) - identity.stepType = XProcConstants.p_identity - /* ??? tangled up in the collectin/expression debacle and unclear to me - val idin = new WithInput(config) - idin.port = "source" - identity.addChild(idin) - */ - other.addChild(identity) - - // If there isn't a primary output, make sure we sink the output of - // the identity step we just added so that one doesn't get added - // automatically - if (primaryOutput.isEmpty) { - val sink = new AtomicStep(config) - sink.stepType = XProcConstants.p_sink - other.addChild(sink) - } - - addChild(other) - other.makeStructureExplicit() - } - - if (p_if) { - var primary = false - for (child <- children[WithOutput]) { - primary = primary || child.primary - } - - if (!primary) { - throw XProcException.xsPrimaryOutputRequired(location) - } - } - } - - override protected[model] def validateStructure(): Unit = { - super.validateStructure() - - var first = true - var primaryOutput = Option.empty[DeclareOutput] - for (child <- allChildren) { - child match { - case _: WithInput => () - case _: WithOutput => () - case when: ChooseBranch => // When or Otherwise - if (first) { - for (child <- when.children[DeclareOutput]) { - if (child.primary) { - primaryOutput = Some(child) - } - } - first = false - } - - var foundPrimary = false - for (child <- when.children[DeclareOutput]) { - if (child.primary) { - foundPrimary = true - if (primaryOutput.isEmpty) { - throw XProcException.xsBadChooseOutputs("#NONE", child.port, location) - } - if (primaryOutput.isDefined && primaryOutput.get.port != child.port) { - throw XProcException.xsBadChooseOutputs(primaryOutput.get.port, child.port, location) - } - } - } - - if (!foundPrimary && primaryOutput.isDefined) { - throw XProcException.xsBadChooseOutputs(primaryOutput.get.port, "#NONE", location) - } - case _ => - throw new RuntimeException(s"Invalid content in $this") - } - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[ContainerStart] - val node = start.addChoose(stepName) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - for (child <- children[Container]) { - child.graphEdges(runtime, _graphNode.get) - } - - val chooseNode = _graphNode.get.asInstanceOf[ChooseStart] - for (branch <- children[ChooseBranch]) { - val branchNode = branch._graphNode.get - for (output <- branch.children[DeclareOutput]) { - runtime.graph.addEdge(branchNode, output.port, chooseNode, output.port) - } - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startChoose(tumble_id, stepName) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endChoose() - } - - override def toString: String = { - s"p:choose $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/ChooseBranch.scala b/src/main/scala/com/xmlcalabash/model/xml/ChooseBranch.scala deleted file mode 100644 index f53c633..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/ChooseBranch.scala +++ /dev/null @@ -1,163 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ChooseStart, Node} -import com.jafpl.steps.Manifold -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.params.XPathBindingParams -import com.xmlcalabash.runtime.{StaticContext, XMLCalabashRuntime, XProcXPathExpression} -import com.xmlcalabash.util.ValueUtils -import net.sf.saxon.s9api.QName - -import scala.collection.mutable - -class ChooseBranch(override val config: XMLCalabash) extends Container(config) with NamedArtifact { - protected var _test = "" - protected var _collection = false - protected var testExpr: XProcXPathExpression = _ - - def test: String = _test - protected[model] def test_=(expr: String): Unit = { - _test = expr - setTestExpr() - } - - def collection: Boolean = _collection - protected[model] def collection_=(coll: String): Unit = { - if (List("true", "1", "yes").contains(coll)) { - _collection = true - } else if (List("false", "0", "no").contains(coll)) { - _collection = false - } else { - throw XProcException.xsBadTypeValue(coll, "xs:boolean", location) - } - setTestExpr() - } - - private def setTestExpr(): Unit = { - val context = new StaticContext(staticContext, this) - val params = new XPathBindingParams(collection) - testExpr = new XProcXPathExpression(context, _test, None, None, Some(params)) - } - - override protected[model] def makeStructureExplicit(): Unit = { - // We don't make the with-input structure explicit here because we want - // to do it after any potential inputs have been turned into pipes. - makeContainerStructureExplicit() - } - - override protected def syntheticDeclaredOutput(): DeclareOutput = { - var portName = "#result" - - if (synthetic) { // This must be an otherwise - // If we've created a synthetic otherwise that has a primary output - // port, make sure the port name we give it is consistent with the - // primary output port of the when's, if they have one. - val choose = parent.get - val when = choose.children[When].head - for (child <- when.children[DeclareOutput]) { - if (child.primary) { - portName = child.port - } - } - } - - val output = new DeclareOutput(config) - output.port = portName - output.primary = true - output.sequence = true - output - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - var winput = firstWithInput - if (winput.isEmpty) { - winput = Some(new WithInput(config)) - winput.get.port = "source" - winput.get.primary = true - addChild(winput.get, firstChild) - } - - val bindings = mutable.HashSet.empty[QName] - bindings ++= staticContext.findVariableRefsInString(_test) - if (bindings.isEmpty) { - val depends = staticContext.dependsOnContextString(_test) - if (!depends) { - //FIXME: WHEN/OTHERWISE CAN BE RESOLVED STATICALLY - } - } else { - val env = environment() - for (ref <- bindings) { - val binding = env.variable(ref) - if (binding.isEmpty) { - throw new RuntimeException("Reference to undefined variable") - } - if (!binding.get.static) { - val pipe = new NamePipe(config, ref, binding.get.tumble_id, binding.get) - addChild(pipe) - } - } - } - } - - override protected[model] def normalizeToPipes(): Unit = { - for (child <- allChildren) { - child.normalizeToPipes() - } - - var copyPipes = true - var winput = firstWithInput - if (winput.isEmpty) { - winput = Some(new WithInput(config)) - winput.get.port = "source" - winput.get.primary = true - addChild(winput.get, firstChild) - } else { - copyPipes = winput.get.children[Pipe].isEmpty - } - - if (copyPipes) { - // A bit hacky, but here's where we need to inject the with-input - val parentWithInput = parent.get.firstWithInput - for (child <- parentWithInput.get.allChildren) { - child match { - case pipe: Pipe => - winput.get.addChild(new Pipe(pipe)) - case _ => - throw new RuntimeException("with input hasn't been normalized to pipes?") - } - } - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[ChooseStart] - val node = start.addWhen(testExpr, stepName, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - for (child <- allChildren) { - child match { - case winput: WithInput => - for (child <- winput.allChildren) { - child match { - case pipe: Pipe => - runtime.graph.addEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, "condition") - case pipe: NamePipe => - pipe.graphEdges(runtime, _graphNode.get) - case _ => - throw new RuntimeException("non-pipe children in p:with-input?") - } - } - case _ => - child.graphEdges(runtime, _graphNode.get) - } - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Container.scala b/src/main/scala/com/xmlcalabash/model/xml/Container.scala deleted file mode 100644 index 4019518..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Container.scala +++ /dev/null @@ -1,367 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.jafpl.steps.{Manifold, PortCardinality, PortSpecification} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.{XMLCalabashRuntime, XmlPortSpecification} -import com.xmlcalabash.runtime.params.{ContentTypeCheckerParams, SelectFilterParams} -import com.xmlcalabash.util.MediaType - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -class Container(override val config: XMLCalabash) extends Step(config) with NamedArtifact { - protected var _outputs = mutable.HashMap.empty[String, DeclareOutput] - - def atomic: Boolean = { - for (child <- allChildren) { - child match { - case _: AtomicStep => - // Synthetic children (inline-loader, etc.) don't count - if (!child.synthetic) { - return false - } - case _: Container => return false - case _ => () - } - } - true - } - - protected[model] def makeContainerStructureExplicit(): Unit = { - var firstChild = Option.empty[Artifact] - var withInput = Option.empty[WithInput] - var lastOutput = Option.empty[DeclareOutput] - var primaryOutput = Option.empty[DeclareOutput] - var lastStep = Option.empty[Step] - - for (child <- allChildren) { - child.makeStructureExplicit() - - if (firstChild.isEmpty) { - firstChild = Some(child) - } - - child match { - case input: WithInput => - if (withInput.isDefined) { - throw new RuntimeException("Only one with-input is allowed") - } - withInput = Some(input) - case output: DeclareOutput => - if (_outputs.contains(output.port)) { - throw new RuntimeException("duplicate output port") - } - _outputs.put(output.port, output) - - lastOutput = Some(output) - if (output.primary) { - if (primaryOutput.isDefined) { - throw XProcException.xsDupPrimaryPort(output.port, primaryOutput.get.port, staticContext.location) - } - primaryOutput = Some(output) - } - case atomic: AtomicStep => - lastStep = Some(atomic) - case compound: Container => - lastStep = Some(compound) - case variable: Variable => - environment().addVariable(variable) - case _ => - () - } - } - - if (_outputs.isEmpty && lastStep.isDefined && lastStep.get.primaryOutput.isDefined) { - val output = syntheticDeclaredOutput() - - val pipe = new Pipe(config) - pipe.step = lastStep.get.stepName - pipe.port = lastStep.get.primaryOutput.get.port - pipe.link = lastStep.get.primaryOutput.get - output.addChild(pipe) - _outputs.put(output.port, output) - lastOutput = Some(output) - - if (firstChild.isDefined) { - addChild(output, firstChild.get) - } else { - addChild(output) - } - } - - if (_outputs.size == 1 && lastOutput.get._primary.isEmpty) { - lastOutput.get.primary = true - } - } - - protected def syntheticDeclaredOutput(): DeclareOutput = { - val output = new DeclareOutput(config) - output.port = "#result" - output.primary = true - output.sequence = true - output - } - - override protected[model] def makeBindingsExplicit(): Unit = { - val env = environment() - - // Be careful here, we don't call super.makeBindingsExplicit() so this method - // has to be kept up-to-date with respect to changes there! - - // Make the bindings for this step's inputs explicit - for (input <- children[WithInput]) { - input.makeBindingsExplicit() - } - - for (sbinding <- env.staticVariables) { - _inScopeStatics.put(sbinding.name.getClarkName, sbinding) - } - - for (dbinding <- env.variables) { - _inScopeDynamics.put(dbinding.name, dbinding) - } - - if (atomic) { - return - } - - var poutput = Option.empty[DeclareOutput] - var lastStep = Option.empty[Step] - for (child <- allChildren) { - child match { - case output: DeclareOutput => - if (output.primary) { - poutput = Some(output) - } - case step: Step => - lastStep = Some(step) - case _ => () - } - } - - if (lastStep.isDefined && poutput.isDefined && poutput.get.bindings.isEmpty) { - val lpo = lastStep.get.primaryOutput - if (lpo.isDefined) { - val pipe = new Pipe(config) - pipe.port = lpo.get.port - pipe.step = lastStep.get.stepName - pipe.link = lpo.get - poutput.get.addChild(pipe) - } - } - - for (child <- allChildren) { - child match { - case option: DeclareOption => - option.makeBindingsExplicit() - if (option.select.isDefined && option.static) { - option.staticValue = computeStatically(option.select.get) - } - case variable: Variable => - variable.makeBindingsExplicit() - if (variable.select.isDefined && variable.static) { - variable.staticValue = computeStatically(variable.select.get) - } - case _: WithInput => - () // we did this above - case _ => - child.makeBindingsExplicit() - } - } - - // Add ContentTypeCheckers and select filters if we need them - if (lastStep.isDefined) { - val innerEnv = lastStep.get.environment() - for (input <- children[DeclareInput]) { - if (input.select.isDefined) { - logger.debug(s"Adding select filter for container input ${stepName}/${input.port}: ${input.select.get}") - val context = staticContext.withStatics(inScopeStatics) - - val ispec = if (input.sequence) { - XmlPortSpecification.ANYSOURCESEQ - } else { - XmlPortSpecification.ANYSOURCE - } - - val params = new SelectFilterParams(context, input.select.get, input.port, ispec) - val filter = new AtomicStep(config, params) - filter.stepType = XProcConstants.cx_select_filter - - addChild(filter) - - val finput = new WithInput(config) - finput.port = "source" - finput.primary = true - filter.addChild(finput) - - val foutput = new WithOutput(config) - foutput.port = "result" - foutput.primary = true - filter.addChild(foutput) - - for (name <- staticContext.findVariableRefsInString(input.select.get)) { - var binding = _inScopeDynamics.get(name) - if (binding.isDefined) { - val npipe = new NamePipe(config, name, binding.get.tumble_id, binding.get) - filter.addChild(npipe) - } else { - binding = _inScopeStatics.get(name.getClarkName) - if (binding.isEmpty) { - throw new RuntimeException(s"Reference to variable not in scope: $name") - } - } - } - - replumb(input, foutput) - - val newpipe = new Pipe(config) - newpipe.step = stepName - newpipe.port = input.port - newpipe.link = input - finput.addChild(newpipe) - } - - if (!MediaType.OCTET_STREAM.allowed(input.contentTypes)) { - logger.debug(s"Adding content-type-checker for container input ${stepName}/${input.port}") - val params = new ContentTypeCheckerParams(input.port, input.contentTypes, staticContext, None, inputPort = true, true) - val atomic = new AtomicStep(config, params) - atomic.stepType = XProcConstants.cx_content_type_checker - addChild(atomic) - - val winput = new WithInput(config) - winput.port = "source" - atomic.addChild(winput) - - val woutput = new WithOutput(config) - woutput.port = "result" - atomic.addChild(woutput) - - replumb(input, woutput) - - val newpipe = new Pipe(config) - newpipe.step = stepName - newpipe.port = input.port - newpipe.link = input - winput.addChild(newpipe) - } - } - - for (output <- children[DeclareOutput]) { - var check = false - //println(s"${stepName}, ${output.port}, ${output.contentTypes}") - for (binding <- output.children[Pipe]) { - val port = binding.port - val step = innerEnv.step(binding.step).get - val outputContentTypes = step match { - case atom: AtomicStep => - val decl = atom.declaration(atom.stepType).get - decl.output(port, None).contentTypes - case cont: Container => - if (cont._outputs.contains(port)) { - cont._outputs(port).contentTypes - } else { - // See if we can find a with-output for this port - var woutput = Option.empty[WithOutput] - for (wo <- cont.children[WithOutput]) { - if (wo.port == port) { - woutput = Some(wo) - } - } - if (woutput.isEmpty) { - // It must be implicit, so no checking is required - List(MediaType.OCTET_STREAM) - } else { - woutput.get.contentTypes - } - } - } - - //println(s" ${step} / ${outputContentTypes}") - for (pct <- outputContentTypes) { - check = check || !pct.allowed(output.contentTypes) - } - } - - if (check) { - logger.debug(s"Adding content-type-checker for container output ${stepName}/${output.port}") - val params = new ContentTypeCheckerParams(output.port, output.contentTypes, staticContext, None, inputPort = false, true) - val atomic = new AtomicStep(config, params) - atomic.stepType = XProcConstants.cx_content_type_checker - addChild(atomic) - - val winput = new WithInput(config) - winput.port = "source" - atomic.addChild(winput) - - val pipes = ListBuffer.empty[Pipe] ++ output.children[Pipe] - for (oldpipe <- pipes) { - output.removeChild(oldpipe) - winput.addChild(oldpipe) - } - - val woutput = new WithOutput(config) - woutput.port = "result" - atomic.addChild(woutput) - - val newpipe = new Pipe(config) - newpipe.step = atomic.stepName - newpipe.port = "result" - newpipe.link = woutput - output.addChild(newpipe) - } - } - } - } - - override protected[model] def validateStructure(): Unit = { - for (child <- allChildren) { - child.validateStructure() - child match { - case _: WithInput => () - case _: WithOutput => () - case _: DeclareInput => () - case _: DeclareOutput => () - case _: Step => () - case _: NamePipe => () // For Viweport - case _: Variable => () - case _ => - throw new RuntimeException(s"Unexpected content in $this: $child") - } - } - } - - def containerManifold: Manifold = { - val spec = mutable.HashMap.empty[String, PortCardinality] - for (output <- _outputs.values) { - if (output.sequence) { - spec.put(output.port, PortCardinality.ZERO_OR_MORE) - } else { - spec.put(output.port, PortCardinality.EXACTLY_ONE) - } - } - new Manifold(Manifold.WILD, new PortSpecification(spec.toMap)) - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - if (allChildren.nonEmpty) { - if (_graphNode.isDefined) { - for (child <- allChildren) { - child match { - case step: Step => - step.graphNodes(runtime, _graphNode.get) - case option: DeclareOption => - option.graphNodes(runtime, _graphNode.get) - case variable: Variable => - variable.graphNodes(runtime, _graphNode.get) - case _ => () - } - } - } else { - println("cannot graphNodes for children of " + this) - } - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/DataSource.scala b/src/main/scala/com/xmlcalabash/model/xml/DataSource.scala deleted file mode 100644 index 38cc554..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/DataSource.scala +++ /dev/null @@ -1,105 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.ImplParams -import net.sf.saxon.s9api.QName - -import scala.collection.mutable.ListBuffer - -abstract class DataSource(override val config: XMLCalabash) extends Artifact(config) { - protected val depends: ListBuffer[String] = ListBuffer.empty[String] - - override protected[model] def validateStructure(): Unit = { - for (child <- allChildren) { - child match { - case _: Pipe => () - case _: NamePipe => () - case _ => - throw new RuntimeException(s"Invalid content in $this") - } - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - // If the ancestor of a data source has a dependency, so does the data source - var p = parent - while (p.isDefined) { - p.get match { - case step: Step => - for (name <- step.depends) { - if (!depends.contains(name)) { - depends += name - } - } - case _ => - () - } - p = p.get.parent - } - } - - protected[model] def normalizeDataSourceToPipes(stepType: QName, params: ImplParams): Unit = { - if (parent.isDefined && parent.get.parent.isDefined) { - parent.get.parent.get match { - case binding: NameBinding => - if (binding.static) { - return // this all has to be resolved statically - } - case _ => () - } - } - - val loader = new AtomicLoader(config, params, this) - loader.stepType = stepType - - for (depend <- depends) { - loader._depends += depend - } - - if (allChildren.nonEmpty) { - val winput = new WithInput(config) - winput.port = "source" - loader.addChild(winput) - for (child <- allChildren) { - winput.addChild(child) - } - removeChildren() - } - - val woutput = new WithOutput(config) - woutput.port = "result" - loader.addChild(woutput) - - val step = if (parent.get.isInstanceOf[WithInput]) { - val p = parent.get.parent.get - // Hack for inline inside with-option - if (p.isInstanceOf[WithOption]) { - p.parent.get - } else { - p - } - } else { - parent.get - } - - var stepTarget: Artifact = step - var container = step.parent.get.asInstanceOf[Container] - if (container.isInstanceOf[Choose]) { - stepTarget = container - container = container.parent.get.asInstanceOf[Container] - } - - container.addChild(loader, stepTarget) - - val pipe = new Pipe(config) - pipe.step = loader.stepName - pipe.port = "result" - pipe.link = woutput - - parent.get.replaceChild(pipe, this) - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/DeclContainer.scala b/src/main/scala/com/xmlcalabash/model/xml/DeclContainer.scala deleted file mode 100644 index b26312f..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/DeclContainer.scala +++ /dev/null @@ -1,135 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.config.StepSignature -import com.xmlcalabash.exceptions.XProcException -import net.sf.saxon.s9api.QName - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -class DeclContainer(override val config: XMLCalabash) extends Container(config) { - protected var _psvi_required = Option.empty[Boolean] - protected var _xpath_version = Option.empty[Double] - protected var _exclude_inline_prefixes = Option.empty[String] - protected var _version = Option.empty[Double] - - protected var _signatures: ListBuffer[StepSignature] = ListBuffer.empty[StepSignature] - private val processed = mutable.HashSet.empty[DeclContainer] - - def inScopeDeclarations: List[StepSignature] = _signatures.toList - - def addSignature(sig: StepSignature): Unit = { - if (sig.stepType.isEmpty) { - return - } - - if (sig.declaration.isDefined) { - // If it's not defined, we're processing a signature within the library that contains it, visibility is irrelevant - val sigcontainer = sig.declaration.get.parent - if (sig.declaration.get.visiblity != "public" - && (sigcontainer.isEmpty || sigcontainer.get != this)) { - // not visible here - return - } - } - - val dsig = declaration(sig.stepType.get) - if (dsig.isDefined) { - if (dsig.get eq sig) { - return // they're the same signature - } else { - throw XProcException.xsDupStepType(sig.stepType.get, location) - } - } - _signatures += sig - } - - def addSignatures(sigs: List[StepSignature]): Unit = { - for (sig <- sigs) { - addSignature(sig) - } - } - - protected[model] def loadImports(): Unit = { - val imap = collection.mutable.HashMap.empty[Artifact,Artifact] - - for (child <- allChildren) { - child match { - case decl: DeclareStep => - decl.parseDeclarationSignature() - decl.loadImports() - case pimport: Import => - val icont = pimport.loadImports() - imap.put(pimport, icont) - case _ => () - } - } - - for ((pimport, icont) <- imap) { - replaceChild(icont, pimport) - } - } - - protected[model] def updateInScopeSteps(): Unit = { - val buf = ListBuffer.empty[DeclContainer] - - for (child <- allChildren) { - child match { - case decl: DeclareStep => - if (!processed.contains(decl)) { - processed += decl - decl.updateInScopeSteps() - } - if (decl.signature.isEmpty) { - decl.parseDeclarationSignature() - } - addSignature(decl.signature.get) - case library: Library => - if (!processed.contains(library)) { - processed += library - library.updateInScopeSteps() - } - addSignatures(library._signatures.toList) - buf += library - case _ => () - } - } - - for (library <- buf) { - removeChild(library) - } - } - - override def declaration(stepType: QName): Option[StepSignature] = { - declaration(stepType, this) - } - - override def declaration(stepType: QName, container: DeclContainer): Option[StepSignature] = { - var found = Option.empty[StepSignature] - - for (sig <- _signatures) { - if (sig.stepType.isDefined && sig.stepType.get == stepType) { - found = Some(sig) - } - } - - if (found.isEmpty) { - if (parent.isDefined) { - found = parent.get.declaration(stepType) - } else { - for (lib <- config.builtinSteps) { - for (sig <- lib.inScopeDeclarations) { - if (found.isEmpty) { - if (sig.stepType.isDefined && sig.stepType.get == stepType) { - found = Some(sig) - } - } - } - } - } - } - - found - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/DeclareFunction.scala b/src/main/scala/com/xmlcalabash/model/xml/DeclareFunction.scala deleted file mode 100644 index bd4da0f..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/DeclareFunction.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash - -class DeclareFunction(override val config: XMLCalabash) extends Artifact(config) { - override protected[model] def makeStructureExplicit(): Unit = { - for (child <- allChildren) { - child.makeStructureExplicit() - } - } - - override protected[model] def validateStructure(): Unit = { - for (child <- allChildren) { - child.validateStructure() - } - } - - override def toString: String = { - s"p:declare-function $tumble_id" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/DeclareInput.scala b/src/main/scala/com/xmlcalabash/model/xml/DeclareInput.scala deleted file mode 100644 index a6f31fe..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/DeclareInput.scala +++ /dev/null @@ -1,64 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.runtime.params.SelectFilterParams -import com.xmlcalabash.util.MediaType -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.XdmNode - -import scala.collection.mutable.ListBuffer - -class DeclareInput(override val config: XMLCalabash) extends Port(config) { - private val _exclude_result_prefixes = List.empty[String] - protected[xmlcalabash] val defaultInputs: ListBuffer[DataSource] = ListBuffer.empty[DataSource] - - def exclude_result_prefixes: List[String] = _exclude_result_prefixes - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(XProcConstants._port)) { - _port = staticContext.parseNCName(attr(XProcConstants._port)).get - } else { - throw XProcException.xsMissingRequiredAttribute(XProcConstants._port, location) - } - - _sequence = staticContext.parseBoolean(attr(XProcConstants._sequence)) - _primary = staticContext.parseBoolean(attr(XProcConstants._primary)) - _select = attr(XProcConstants._select) - - _content_types = staticContext.parseContentTypes(attr(XProcConstants._content_types)) - if (_content_types.isEmpty) { - _content_types = List(MediaType.OCTET_STREAM) - } - - _href = attr(XProcConstants._href) - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - for (child <- allChildren) { - child.graphEdges(runtime, parent) - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startInput(tumble_id, tumble_id, port) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endInput() - } - - override def toString: String = { - s"p:input $port $tumble_id" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/DeclareOption.scala b/src/main/scala/com/xmlcalabash/model/xml/DeclareOption.scala deleted file mode 100644 index 8ea6721..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/DeclareOption.scala +++ /dev/null @@ -1,176 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.{ExceptionCode, ModelException, XProcException} -import com.xmlcalabash.messages.XdmValueItemMessage -import com.xmlcalabash.runtime.params.XPathBindingParams -import com.xmlcalabash.runtime.{ExprParams, XMLCalabashRuntime, XProcMetadata, XProcXPathExpression, XProcXPathValue} -import com.xmlcalabash.util.{S9Api, XProcVarValue} -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, SaxonApiException, SequenceType, XdmMap, XdmValue} -import net.sf.saxon.trans.XPathException - -import java.net.URI -import scala.collection.mutable - -class DeclareOption(override val config: XMLCalabash) extends NameBinding(config) { - private var _runtimeBindings = Map.empty[QName,XProcVarValue] - - override def toString: String = { - s"p:option $name $tumble_id" - } - - override def declaredType: SequenceType = { - if (_as.isEmpty) { - _declaredType = staticContext.parseSequenceType(Some("Q{http://www.w3.org/2001/XMLSchema}string")) - } else { - _declaredType = _as - } - _declaredType.get - } - - override protected[model] def validateStructure(): Unit = { - for (child <- allChildren) { - child match { - case _: WithInput => () - case _: NamePipe => () - case _ => - throw new RuntimeException(s"Invalid content in $this") - } - } - } - - def runtimeBindings(bindings: Map[QName, XProcVarValue]): Unit = { - _runtimeBindings = bindings - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - val env = environment() - - val bindings = mutable.HashSet.empty[QName] - if (_select.isDefined) { - bindings ++= staticContext.findVariableRefsInString(_select.get) - } - - var nonStaticBindings = false - for (ref <- bindings) { - val binding = env.variable(ref) - if (binding.isEmpty) { - throw new RuntimeException("Reference to undefined variable") - } - nonStaticBindings = nonStaticBindings || !binding.get.static - } - - if (nonStaticBindings) { - var winput = firstWithInput - if (winput.isEmpty) { - val input = new WithInput(config) - input.port = "source" - addChild(input) - winput = Some(input) - } - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - if (static) { - // Statics have already been evaluated, they don't appear in the graph - return - } - - val container = this.parent.get - val cnode = container._graphNode.get.asInstanceOf[ContainerStart] - if (cnode.parent.nonEmpty) { - throw new ModelException(ExceptionCode.INTERNAL, "Don't know what to do about opts here", location) - } - - val params = new XPathBindingParams(collection) - val init = if (_runtimeBindings.contains(name)) { - new XProcXPathValue(staticContext, _runtimeBindings(name), as, _allowedValues, params) - } else { - val bindings = mutable.HashSet.empty[QName] - if (_select.isDefined) { - bindings ++= staticContext.findVariableRefsInString(_select.get) - } - - val extext = _select.getOrElse("()") - val expr = new XProcXPathExpression(staticContext, extext, as, _allowedValues, params) - - // Let's see if we think this is a syntactically valid expression (see bug #506 and test ab-option-024) - val xcomp = config.processor.newXPathCompiler() - xcomp.setBaseURI(URI.create("http://example.com/")) - for (varname <- bindings) { - xcomp.declareVariable(varname) - } - for ((prefix, uri) <- staticContext.nsBindings) { - xcomp.declareNamespace(prefix, uri) - } - try { - xcomp.compile(extext) - } catch { - case sae: SaxonApiException => - sae.getCause match { - case _: XPathException => - throw XProcException.xsStaticErrorInExpression(extext, sae.getMessage, location) - case _ => throw sae - } - case other: Throwable => - throw other - } - - expr - } - - val node = parent.asInstanceOf[ContainerStart].addOption(_name.getClarkName, init, xpathBindingParams(), topLevel = true) - - for (np <- _dependentNameBindings) { - val binding = findInScopeOption(np.name) - if (binding.isEmpty) { - throw XProcException.xsNoBindingInExpression(np.name, location) - } - np.patchNode(binding.get.graphNode.get) - np.graphEdges(runtime, node) - } - - _graphNode = Some(node) - } - - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startOption(tumble_id, tumble_id, name) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endOption() - } - - override def graphEdges(runtime: XMLCalabashRuntime, parNode: Node): Unit = { - if (static) { - return - } - - val env = environment() - for (stepName <- depends) { - val step = env.step(stepName) - if (step.isEmpty) { - throw XProcException.xsNotAStep(stepName, location) - } else { - _graphNode.get.dependsOn(step.get._graphNode.get) - } - } - - val toNode = parNode - val fromPort = "result" - val fromNode = _graphNode.get - val toPort = "#bindings" - runtime.graph.addEdge(fromNode, fromPort, toNode, toPort) - - for (child <- allChildren) { - child.graphEdges(runtime, _graphNode.get) - } - } -} - diff --git a/src/main/scala/com/xmlcalabash/model/xml/DeclareOutput.scala b/src/main/scala/com/xmlcalabash/model/xml/DeclareOutput.scala deleted file mode 100644 index 8a6b2d1..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/DeclareOutput.scala +++ /dev/null @@ -1,89 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.{MediaType, S9Api} -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmMap, XdmNode} - -import scala.collection.mutable -import scala.jdk.CollectionConverters.MapHasAsScala - -class DeclareOutput(override val config: XMLCalabash) extends Port(config) { - private var mapexpr = Option.empty[String] - private val _serialization = mutable.Map.empty[QName,String] - - def serialization: Map[QName,String] = _serialization.toMap - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(XProcConstants._port)) { - _port = staticContext.parseNCName(attr(XProcConstants._port)).get - } else { - throw new RuntimeException("Port is required") - } - _sequence = staticContext.parseBoolean(attr(XProcConstants._sequence)) - _primary = staticContext.parseBoolean(attr(XProcConstants._primary)) - - _content_types = staticContext.parseContentTypes(attr(XProcConstants._content_types)) - if (_content_types.isEmpty) { - _content_types = List(MediaType.OCTET_STREAM) - } - - if (attributes.contains(XProcConstants._serialization)) { - mapexpr = attr(XProcConstants._serialization) - } - - _href = attr(XProcConstants._href) - _pipe = attr(XProcConstants._pipe) - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - super.makeStructureExplicit() - examineBindings() - } - - override def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - if (mapexpr.isDefined) { - val msg = computeStatically(mapexpr.get) - msg.item match { - case map: XdmMap => - val sermap = S9Api.forceQNameKeys(map.getUnderlyingValue, staticContext) - for ((key,value) <- sermap.asImmutableMap().asScala) { - val name = key.getQNameValue - _serialization(name) = value.getUnderlyingValue.getStringValue - } - case _ => - throw new RuntimeException("Serialization options must be a map") - } - } - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - for (child <- allChildren) { - child.graphEdges(runtime, parent) - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startOutput(tumble_id, tumble_id, port) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endOutput() - } - - override def toString: String = { - s"p:output $port${if (sequence) "*" else ""} $tumble_id" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/DeclareStep.scala b/src/main/scala/com/xmlcalabash/model/xml/DeclareStep.scala deleted file mode 100644 index 1a30692..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/DeclareStep.scala +++ /dev/null @@ -1,384 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.exceptions.JafplLoopDetected -import com.jafpl.graph.Node -import com.jafpl.steps.{Manifold, PortCardinality, PortSpecification} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.config.{OptionSignature, PortSignature, StepSignature} -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.runtime.params.ContentTypeCheckerParams -import com.xmlcalabash.util.xc.ElaboratedPipeline -import com.xmlcalabash.util.{S9Api, TypeUtils, XProcVarValue} -import net.sf.saxon.s9api.{ItemType, QName, SaxonApiException, XdmAtomicValue, XdmNode, XdmNodeKind} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -class DeclareStep(override val config: XMLCalabash) extends DeclContainer(config) { - private var _type = Option.empty[QName] - private var _visibility = Option.empty[String] - private var _signature = Option.empty[StepSignature] - private val _inputs = mutable.HashMap.empty[String, DeclareInput] - private val _bindings = mutable.HashMap.empty[QName, DeclareOption] - private var _excludeUriBindings = Set.empty[String] - - // These are a bit of a hack. It's very hard to tell the difference - // between an inline declare-step and an imported declare-step. - // We have to process them, but we have to not process them twice. - private var madeStructureExplicit = false - private var madeBindingsExplicit = false - private var didValidateStructure = false - - def stepType: Option[QName] = _type - def psvi_required: Option[Boolean] = _psvi_required - def xpath_version: Option[Double] = _xpath_version - def exclude_inline_prefixes: Option[String] = _exclude_inline_prefixes - def exclude_uri_bindings: Set[String] = _excludeUriBindings - def version: Double = _version.getOrElse(3.0) - def visiblity: String = _visibility.getOrElse("public") - def signature: Option[StepSignature] = _signature - - def inputPorts: List[String] = _inputs.keySet.toList - def outputPorts: List[String] = _outputs.keySet.toList - - def input(port: String): DeclareInput = _inputs(port) - def output(port: String): DeclareOutput = _outputs(port) - - def inputs: List[DeclareInput] = _inputs.values.toList - def outputs: List[DeclareOutput] = _outputs.values.toList - - def bindings: Map[QName,DeclareOption] = _bindings.toMap - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - val aname = attr(XProcConstants._name) - if (aname.isDefined) { - try { - val ncname = TypeUtils.castAtomicAs(XdmAtomicValue.makeAtomicValue(aname.get), ItemType.NCNAME, staticContext) - _name = Some(ncname.getStringValue) - } catch { - case _: SaxonApiException => - throw XProcException.xsBadTypeValue(aname.get, "NCName", location) - } - } - - _type = staticContext.parseQName(attr(XProcConstants._type)) - _psvi_required = staticContext.parseBoolean(attr(XProcConstants._psvi_required)) - if (attributes.contains(XProcConstants._xpath_version)) { - val vstr = attr(XProcConstants._xpath_version).get - _xpath_version = Some(vstr.toDouble) - } - if (attributes.contains(XProcConstants._version)) { - val vstr = attr(XProcConstants._version).get - try { - _version = Some(vstr.toDouble) - } catch { - case _: NumberFormatException => - throw XProcException.xsBadVersion(vstr, location) - } - if (_version.get != 3.0) { - throw XProcException.xsInvalidVersion(_version.get, location) - } - } - if (_version.isEmpty) { - if (node.getParent.getNodeKind == XdmNodeKind.DOCUMENT) { - throw XProcException.xsVersionRequired(location) - } - } - - _exclude_inline_prefixes = attr(XProcConstants._exclude_inline_prefixes) - if (_exclude_inline_prefixes.isDefined) { - val prefixes = _exclude_inline_prefixes.get.split("\\s+").toList - _excludeUriBindings = S9Api.urisForPrefixes(node, prefixes.toSet) - } - - _visibility = attr(XProcConstants._visibility) - if (_visibility.isDefined) { - if (_visibility.get != "public" && _visibility.get != "private") { - throw XProcException.xdBadVisibility(_visibility.get, location) - } - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - protected[xml] def parseDeclarationSignature(): Unit = { - // If there's only one input and it doesn't have a declared primary status, make it primary - val pinput = children[DeclareInput].headOption - if (children[DeclareInput].length == 1 && pinput.get._primary.isEmpty) { - pinput.get.primary = true - } - - // If any of the inputs have defaults, record them - for (declinput <- children[DeclareInput]) { - declinput.examineBindings() - if (declinput.children[DataSource].nonEmpty) { - declinput.defaultInputs ++= declinput.children[DataSource] - // then remove them - declinput.removeChildren() - } - } - - // If there's only one output and it doesn't have a declared primary status, make it primary - val poutput = children[DeclareOutput].headOption - if (children[DeclareOutput].length == 1 && poutput.get._primary.isEmpty) { - poutput.get.primary = true - } - - var lastInput = Option.empty[DeclareInput] - var lastOutput = Option.empty[DeclareOutput] - var primaryInput = Option.empty[DeclareInput] - var primaryOutput = Option.empty[DeclareOutput] - - for (child <- allChildren) { - child match { - case input: DeclareInput => - if (_inputs.contains(input.port) || _outputs.contains(input.port)) { - throw XProcException.xsDupPortName(input.port, location) - } - _inputs.put(input.port, input) - - lastInput = Some(input) - if (input.primary) { - if (primaryInput.isDefined) { - throw XProcException.xsDupPrimaryPort(input.port, primaryInput.get.port, staticContext.location) - } - primaryInput = Some(input) - } - case output: DeclareOutput => - if (_outputs.contains(output.port) || _inputs.contains(output.port)) { - throw XProcException.xsDupPortName(output.port, location) - } - _outputs.put(output.port, output) - - lastOutput = Some(output) - if (output.primary) { - if (primaryOutput.isDefined) { - throw XProcException.xsDupPrimaryPort(output.port, primaryOutput.get.port, staticContext.location) - } - primaryOutput = Some(output) - } - case option: DeclareOption => - if (_bindings.contains(option.name)) { - throw XProcException.xsDuplicateOptionName(option.name, location) - } - _bindings.put(option.name, option) - - case _ => () - } - } - - if (_inputs.size == 1 && lastInput.get._primary.isEmpty) { - lastInput.get.primary = true - } - - if (_outputs.size == 1 && lastOutput.get._primary.isEmpty) { - lastOutput.get.primary = true - } - - val stepSig = new StepSignature(stepType) - if (stepType.isDefined) { - if (config.atomicStepImplementation(stepType.get).isDefined) { - stepSig.implementation = config.atomicStepImplementation(stepType.get).get - } else { - stepSig.declaration = this - if (atomic) { - logger.info(s"No implementation for declared ${stepType.get} step") - } - } - } - - for (child <- allChildren) { - child match { - case input: DeclareInput => - val portSig = new PortSignature(input.port, input.primary, input.sequence, input.defaultInputs.toList) - portSig.contentTypes = input.contentTypes - stepSig.addInput(portSig, input.location.get) - case output: DeclareOutput => - val portSig = new PortSignature(output.port, output.primary, output.sequence) - portSig.contentTypes = output.contentTypes - stepSig.addOutput(portSig, output.location.get) - case option: DeclareOption => - val optSig = new OptionSignature(option.name, option.declaredType, option.required) - if (option.allowedValues.isDefined) { - optSig.tokenList = option.allowedValues.get - } - if (option.select.isDefined) { - optSig.defaultSelect = option.select.get - } - optSig.forceQNameKeys = option.qnameKeys - stepSig.addOption(optSig, option.location.get) - case _ => - () - } - } - - _signature = Some(stepSig) - } - - override protected[model] def makeStructureExplicit(): Unit = { - if (madeStructureExplicit) { - return - } - madeStructureExplicit = true - - super.makeStructureExplicit() - - // If any of the inputs have defaults, make sure we make the structure explicit - for (declinput <- children[DeclareInput]) { - for (source <- declinput.defaultInputs) { - source.makeStructureExplicit() - } - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - if (madeBindingsExplicit) { - return - } - madeBindingsExplicit = true - - super.makeBindingsExplicit() - - // If any of the inputs have defaults, make sure we make the bindings explicit - for (declinput <- children[DeclareInput]) { - for (source <- declinput.defaultInputs) { - source.makeBindingsExplicit() - } - } - } - - override protected[model] def validateStructure(): Unit = { - if (didValidateStructure) { - return - } - didValidateStructure = true - - val buf = ListBuffer.empty[Artifact] - - for (child <- allChildren) { - child.validateStructure() - child match { - case decl: DeclareStep => - buf += decl - case _ => () - } - } - - for (decl <- buf) { - removeChild(decl) - } - - if (!atomic) { - normalizeToPipes() - addFilters() - } - } - - def runtime(): XMLCalabashRuntime = { - val runtime = new XMLCalabashRuntime(this) - val pipeline = runtime.graph.addPipeline(stepName, manifold) - - for (port <- inputPorts) { - runtime.graph.addInput(pipeline, port) - } - - for (port <- outputPorts) { - runtime.graph.addOutput(pipeline, port) - } - - _graphNode = Some(pipeline) - - graphNodes(runtime, pipeline) - - // Look for name bindings that have to be patched - val declOptions = mutable.HashMap.empty[NameBinding, Node] - for (child <- children[DeclareOption]) { - if (!child.static) { - declOptions.put(child, child._graphNode.get) - } - } - for (nb <- findDescendants[NamePipe]) { - if (declOptions.contains(nb.link)) { - nb.patchNode(declOptions(nb.link)) - } - } - - for (child <- allChildren) { - child match { - case _: Documentation => () - case _: PipeInfo => () - case _ => - child.graphEdges(runtime, pipeline) - } - } - - try { - runtime.init(this) - } catch { - case _: JafplLoopDetected => - throw XProcException.xsLoop("???", "???", location) - } - runtime - } - - private def manifold: Manifold = { - val inputMap = mutable.HashMap.empty[String,PortCardinality] - for (input <- inputs) { - if (input.sequence) { - inputMap.put(input.port, PortCardinality.ZERO_OR_MORE) - } else { - if (input.defaultInputs.nonEmpty) { - // Allow it to be empty, the default will provide something - inputMap.put(input.port, new PortCardinality(0, 1)) - } else { - inputMap.put(input.port, PortCardinality.EXACTLY_ONE) - } - } - } - val outputMap = mutable.HashMap.empty[String,PortCardinality] - for (output <- outputs) { - if (output.sequence) { - outputMap.put(output.port, PortCardinality.ZERO_OR_MORE) - } else { - outputMap.put(output.port, PortCardinality.EXACTLY_ONE) - } - } - new Manifold(new PortSpecification(inputMap.toMap), new PortSpecification(outputMap.toMap)) - } - - def patchOptions(bindings: Map[QName,XProcVarValue]): Unit = { - for (child <- children[DeclareOption]) { - child.runtimeBindings(bindings) - } - } - - def xdump: XdmNode = { - val xml = new ElaboratedPipeline(config) - xdump(xml) - val doc = xml.endPipeline() - doc.get - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startPipeline(tumble_id, stepName, stepType, version, psvi_required, xpath_version, exclude_inline_prefixes, _visibility) - for (child <- rawChildren) { - child.xdump(xml) - } - } - - override def toString: String = { - if (stepType.isDefined) { - s"p:declare-step ${stepType.get} $stepName $uid" - } else { - s"p:declare-step {anon} $stepName $uid" - } - } - -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Document.scala b/src/main/scala/com/xmlcalabash/model/xml/Document.scala deleted file mode 100644 index 7ffc478..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Document.scala +++ /dev/null @@ -1,128 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.config.{DocumentRequest, DocumentResponse} -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XProcVtExpression -import com.xmlcalabash.runtime.params.DocumentLoaderParams -import com.xmlcalabash.util.MediaType -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmNode} - -import java.net.URI -import scala.collection.mutable - -class Document(override val config: XMLCalabash) extends DataSource(config) { - private var _href = "UNINITIALIZED" - private var _hrefAvt = List.empty[String] - private var _contentType = Option.empty[MediaType] - private var _documentProperties = Option.empty[String] - private var _parameters = Option.empty[String] - private var _context_provided = false - - def this(copy: Document) = { - this(copy.config) - depends ++= copy.depends - _href = copy._href - _hrefAvt = copy._hrefAvt - _contentType = copy._contentType - _documentProperties = copy._documentProperties - _parameters = copy._parameters - _context_provided = copy._context_provided - _staticContext = copy._staticContext - } - - def href: String = _href - protected[model] def hrefAvt: List[String] = _hrefAvt - protected[model] def href_=(href: String): Unit = { - _href = href - _hrefAvt = staticContext.parseAvt(_href) - } - def content_type: Option[MediaType] = _contentType - def document_properties: Option[String] = _documentProperties - def parameters: Option[String] = _parameters - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (!attributes.contains(XProcConstants._href)) { - throw XProcException.xsMissingRequiredAttribute(XProcConstants._href, location) - } - - _href = attr(XProcConstants._href).get - _hrefAvt = staticContext.parseAvt(_href) - _contentType = MediaType.parse(attr(XProcConstants._content_type)) - _documentProperties = attr(XProcConstants._document_properties) - _parameters = attr(XProcConstants._parameters) - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - // nop - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - val env = environment() - if (allChildren.isEmpty && env.defaultReadablePort.isDefined && !parent.get.isInstanceOf[DeclareInput]) { - _context_provided = true - val pipe = new Pipe(config) - pipe.link = env.defaultReadablePort.get - pipe.port = env.defaultReadablePort.get.port - pipe.step = env.defaultReadablePort.get.step.stepName - addChild(pipe) - } - - val bindings = mutable.HashSet.empty[QName] - bindings ++= staticContext.findVariableRefsInAvt(_hrefAvt) - bindings ++= staticContext.findVariableRefsInString(_documentProperties) - bindings ++= staticContext.findVariableRefsInString(_parameters) - - for (ref <- bindings) { - val binding = env.variable(ref) - if (binding.isEmpty) { - throw new RuntimeException("Reference to undefined variable") - } - if (!binding.get.static) { - val pipe = new NamePipe(config, ref, binding.get.tumble_id, binding.get) - addChild(pipe) - } - } - } - - override protected[model] def normalizeToPipes(): Unit = { - val context = staticContext.withStatics(inScopeStatics) - val params = new DocumentLoaderParams(_hrefAvt, _contentType, _parameters, _documentProperties, _context_provided, context) - normalizeDataSourceToPipes(XProcConstants.cx_document_loader, params) - } - - def loadDocument(): DocumentRequest = { - val context = staticContext.withStatics(inScopeStatics) - val expr = new XProcVtExpression(context, _hrefAvt, true) - val msg = config.expressionEvaluator.value(expr, List(), inScopeStatics, None) - val href = if (context.baseURI.isDefined) { - context.baseURI.get.resolve(msg.item.toString) - } else { - new URI(msg.item.toString) - } - new DocumentRequest(href, _contentType, location) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startDocument(tumble_id, _href) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endDocument() - } - - override def toString: String = { - s"p:document $href" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Documentation.scala b/src/main/scala/com/xmlcalabash/model/xml/Documentation.scala deleted file mode 100644 index c6ba5ff..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Documentation.scala +++ /dev/null @@ -1,32 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{Axis, QName, XdmNode, XdmNodeKind} - -class Documentation(override val config: XMLCalabash, val docs: XdmNode) extends Artifact(config) { - override protected[model] def validateStructure(): Unit = { - // nop - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - var root = Option.empty[QName] - val iter = docs.axisIterator(Axis.CHILD) - while (root.isEmpty && iter.hasNext) { - val item = iter.next() - if (item.getNodeKind == XdmNodeKind.ELEMENT) { - root = Some(item.getNodeName) - } - } - - xml.startDocumentation(tumble_id, root) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endDocumentation() - } - - override def toString: String = { - s"p:documentation $tumble_id" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Empty.scala b/src/main/scala/com/xmlcalabash/model/xml/Empty.scala deleted file mode 100644 index 2da70c2..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Empty.scala +++ /dev/null @@ -1,42 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.runtime.params.EmptyLoaderParams -import com.xmlcalabash.util.xc.ElaboratedPipeline - -class Empty(override val config: XMLCalabash) extends DataSource(config) { - - def this(copy: Empty) = { - this(copy.config) - depends ++= copy.depends - } - - override protected[model] def normalizeToPipes(): Unit = { - val params = new EmptyLoaderParams(staticContext) - normalizeDataSourceToPipes(XProcConstants.cx_empty_loader, params) - } - - override protected[model] def makeStructureExplicit(): Unit = { - // nop - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - // nop - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startEmpty(tumble_id) - xml.endEmpty() - } - - override def toString: String = { - if (tumble_id.startsWith("!syn")) { - s"p:empty" - } else { - s"p:empty $tumble_id" - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Environment.scala b/src/main/scala/com/xmlcalabash/model/xml/Environment.scala deleted file mode 100644 index 4ca6830..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Environment.scala +++ /dev/null @@ -1,439 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.sun.org.apache.xpath.internal.XPathProcessorException -import com.xmlcalabash.exceptions.XProcException -import net.sf.saxon.s9api.QName - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -// You'd think that the environment just inherited down the tree as you went, -// but it's more complicated than that. What's in and out of the environment -// is quite different inside a compound step than it is outside. So instead of -// trying to finesse it as we walk down the tree, this object computes the -// environment for the step identified in its constructor. -// -// N.B. Do not cache this; you'd think it would be a performance enhancement, -// but dealing with select expressions sometimes adds filters and that -// changes the default readable port. - -object Environment { - def newEnvironment(step: Artifact): Environment = { - // Walk "up" the tree until we find a valid starting point - step match { - case _: Step => () - case _: DeclareInput => () - case _: DeclareOutput => () - case _: Variable => () - case _: DeclareOption => () - case _: WithOption => () - case _ => - return Environment.newEnvironment(step.parent.get) - } - - val env = new Environment() - - var ancestors = ListBuffer.empty[Artifact] // includes self for compound steps - - // If we start at a DeclareOption, then we're really in a pipeline that we're - // running for a step. Stop at the first atomic step ancestor... - val stopAtStep = step.isInstanceOf[DeclareOption] - var scopeRoot = Option.empty[Artifact] - - var pptr: Option[Artifact] = Some(step) - while (pptr.isDefined) { - var next = pptr.get.parent - pptr.get match { - case variable: Variable => - ancestors.insert(0, variable) - case option: DeclareOption => - ancestors.insert(0, option) - case input: DeclareInput => - ancestors.insert(0, input) - case output: DeclareOutput => - ancestors.insert(0, output) - case library: Library => - ancestors.insert(0, library) - case option: WithOption => - ancestors.insert(0, option) - case step: Step => - if (stopAtStep && scopeRoot.isEmpty) { - scopeRoot = Some(step) - } - ancestors.insert(0, step) - case _ => () - } - - pptr = next - } - - if (scopeRoot.isDefined) { - // The scopeRoot is really the top of the ancestor list, *except* that any static - // variables declared "above" this root are in scope. - val senv = Environment.newEnvironment(scopeRoot.get) - for (opt <- senv.staticVariables) { - if (opt ne step) { - env.addVariable(opt) - } - } - - while (ancestors.nonEmpty && (ancestors.head ne scopeRoot.get)) { - ancestors = ancestors.drop(1) - } - if (ancestors.isEmpty) { - throw XProcException.xiThisCantHappen("Failed to find scopeRoot in ancestors when creating new environment", None) - } - ancestors = ancestors.drop(1) - } - - walk(env, ancestors.toList) - } - - private def walk(env: Environment, ancestors: List[Artifact]): Environment = { - if (ancestors.isEmpty) { - return env - } - - val head = ancestors.head - val next = ancestors.tail.headOption - - if (head.isInstanceOf[DeclareStep]) { - // The only thing that survives passing into a declare step are statics - env.clearSteps() - env.clearPorts() - env.clearDynamicVariables() - } - - head match { - case wi: WithOption => - if (wi.parent.isDefined) { - wi.parent.get match { - case atom: AtomicStep => - val stepsig = atom.declaration(atom.stepType) - // This appears not to be defined for built-in steps. I'm not sure why, but none - // of them have options that depend on preceding options, so I'm not worrying - // about it today. - if (stepsig.get.declaration.isDefined) { - val decl = stepsig.get.declaration.get - var found = false - for (opt <- decl.children[DeclareOption]) { - found = found || opt.name == wi.name - if (!found) { - env.addVariable(opt) - } - } - } - env - case _ => - throw XProcException.xiThisCantHappen("Parent of p:with-option is not an atomic step?", None) - } - } else { - throw XProcException.xiThisCantHappen("Parent of p:with-option is undefined?", None) - } - case lib: Library => - env.defaultReadablePort = None - - // Libraries are a special case, they aren't in the children of the container - if (next.get.isInstanceOf[Library]) { - return walk(env, ancestors.tail) - } - - // Now walk down to the next ancestor - for (child <- lib.allChildren) { - if (next.get == child) { - return walk(env, ancestors.tail) - } - - child match { - case variable: Variable => - env.addVariable(variable) - case option: DeclareOption => - env.addVariable(option) - case childstep: Step => - if (next.isDefined && next.get == childstep) { - return walk(env, ancestors.tail) - } - case _ => () - } - } - - // If we fell off the bottom of this loop, something has gone terribly wrong - throw new RuntimeException("Fell off ancestor list in computing environment") - - case xstep: Container => - // DeclareStep is special; if it's the last ancestor, then it's the root of - // the pipeline and that doesn't get to read its own inputs. If it's not - // the root, then we're setting up the environment for one of its contained - // steps. - - // This step is in the environment - env.addStep(xstep) - - // Its options are in-scope - for (option <- xstep.children[DeclareOption]) { - env.addVariable(option) - } - - if (next.isDefined) { - // Its inputs are readable - for (port <- xstep.children[DeclareInput]) { - if (port.primary) { - env.defaultReadablePort = port - } - env.addPort(port) - } - - xstep match { - // Choose, when, etc., aren't ordinary container steps - case container: Choose => () - //case container: When => () - //case container: Otherwise => () - case container: Try => () - //case container: Catch => () - //case container: Finally => () - case _ => - // Entering a declare-step resets the default readable port - if (xstep.isInstanceOf[DeclareStep]) { - env.defaultReadablePort = xstep.primaryInput - } - - // The outputs of all contained steps are mutually readable - for (child <- xstep.allChildren) { - child match { - case decl: DeclareStep => () // these don't count - case childstep: Container => - if (next.isDefined && next.get == childstep) { - // ignore this one, we'll be diving down into it - } else { - env.addStep(childstep) - for (port <- childstep.children[WithOutput]) { - env.addPort(port) - } - } - case childstep: Step => - // Yes, this can add the output of the step who's environment - // we're computing to the list of readable ports. Doing so - // is a loop, it'll be caught elsewhere. - env.addStep(childstep) - for (port <- childstep.children[WithOutput]) { - env.addPort(port) - } - case _ => () - } - } - } - } - - if (next.isEmpty) { - return env - } - - // Libraries are a special case, they aren't in the children of the container - if (next.get.isInstanceOf[Library]) { - return walk(env, ancestors.tail) - } - - // Now walk down to the next ancestor, calculating the drp - for (child <- xstep.allChildren) { - if (next.get eq child) { - return walk(env, ancestors.tail) - } - xstep match { - // The children of choose and try aren't ordinary children - case _: Choose => () - case _: Try => () - case _ => - child match { - case option: DeclareOption => - env.addVariable(option) - case variable: Variable => - env.addVariable(variable) - case _: DeclareStep => - () - case childstep: Step => - env.defaultReadablePort = childstep.primaryOutput - case _ => - () - } - } - } - - next.get match { - case _: DeclareStep => - return walk(env, ancestors.tail) - case _ => - // If we fell off the bottom of this loop, something has gone terribly wrong - throw new RuntimeException("Fell off ancestor list in container") - } - - case step: AtomicStep => - if (next.isEmpty) { - // This is us. - return env - } else { - return walk(env, ancestors.tail) - } - - case option: DeclareOption => - if (next.isEmpty) { - // This is us. Any preceding options are in-scope - val parent = option.parent.get - var found = false - for (child <- parent.allChildren) { - child match { - case opt: DeclareOption => - found = found || (opt eq option) - if (!found) { - env.addVariable(opt) - } - case _ => () - } - } - return env - } - - // If we got here, something has gone terribly wrong - throw new RuntimeException("Option with children?") - - case variable: Variable => - if (next.isEmpty) { - // This is us. - return env - } - - // If we got here, something has gone terribly wrong - throw new RuntimeException("Variable with children?") - - case input: DeclareInput => - if (next.isEmpty) { - // This is us. - return env - } - - // If we got here, something has gone terribly wrong - throw new RuntimeException("Input with children?") - - case output: DeclareOutput => - if (next.isEmpty) { - // This is us. - // The default readable port from here is the primary output - // of the last step in the pipeline, if the last step has - // a primary output. - var lastchild = Option.empty[Step] - for (child <- output.parent.get.children[Step]) { - lastchild = Some(child) - } - if (lastchild.isDefined) { - env.defaultReadablePort = lastchild.get.primaryOutput - } - - return env - } - - // If we got here, something has gone terribly wrong - throw new RuntimeException("Output with children?") - - case _ => throw new RuntimeException(s"Unexpected in list of ancestors: $head") - } - } -} - -class Environment private() { - private val _inScopeSteps = mutable.HashMap.empty[String, Step] - private val _inScopePorts = mutable.HashMap.empty[String, Port] - private val _inScopeVariables = mutable.HashMap.empty[QName,NameBinding] - private var _defaultReadablePort = Option.empty[Port] - - def defaultReadablePort: Option[Port] = _defaultReadablePort - protected[xml] def defaultReadablePort_=(port: Port): Unit = { - defaultReadablePort = Some(port) - } - protected[xml] def defaultReadablePort_=(port: Option[Port]): Unit = { - _defaultReadablePort = port - } - private def clearSteps(): Unit = { - _inScopeSteps.clear() - } - - def addStep(step: Step): Unit = { - addName(step.stepName, step) - } - - private def clearPorts(): Unit = { - _inScopePorts.clear() - } - - def addPort(port: Port): Unit = { - val name = port.parent.get match { - case step: Step => step.stepName - case _ => - throw new RuntimeException("Parent of port isn't a step?") - } - if (!name.startsWith("!")) { - _inScopePorts.put(s"$name/${port.port}", port) - } - } - - private def addName(name: String, step: Step): Unit = { - if (_inScopeSteps.contains(name)) { - throw XProcException.xsDuplicateStepName(name, None) - } - _inScopeSteps.put(name, step) - } - - def addVariable(binding: NameBinding): Unit = { - _inScopeVariables.put(binding.name, binding) - } - - private def clearVariables(): Unit = { - _inScopeVariables.clear() - } - - private def clearDynamicVariables(): Unit = { - val statics = mutable.HashMap.empty[QName,NameBinding] - for (static <- staticVariables) { - statics.put(static.name, static) - } - _inScopeVariables.clear() - _inScopeVariables ++= statics - } - - - def step(name: String): Option[Step] = _inScopeSteps.get(name) - - def port(stepName: String, portName: String): Option[Port] = { - if (step(stepName).isDefined) { - _inScopePorts.get(s"$stepName/$portName") - } else { - None - } - } - - def variable(name: QName): Option[NameBinding] = { - if (_inScopeVariables.contains(name)) { - _inScopeVariables.get(name) - } else { - None - } - } - - def variables: List[NameBinding] = { - val lb = ListBuffer.empty[NameBinding] - for ((name, variable) <- _inScopeVariables) { - if (!variable.static) { - lb += variable - } - } - lb.toList - } - - def staticVariables: List[NameBinding] = { - val lb = ListBuffer.empty[NameBinding] - for ((name, variable) <- _inScopeVariables) { - if (variable.static) { - lb += variable - } - } - lb.toList - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Finally.scala b/src/main/scala/com/xmlcalabash/model/xml/Finally.scala deleted file mode 100644 index 8ec2ee7..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Finally.scala +++ /dev/null @@ -1,61 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{Node, TryCatchStart} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.XdmNode - -class Finally(override val config: XMLCalabash) extends Container(config) with NamedArtifact { - override def parse(node: XdmNode): Unit = { - super.parse(node) - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - val input = new DeclareInput(config) - input.port = "error" - input.sequence = true - input.primary = true - addChild(input, firstChild) - - makeContainerStructureExplicit() - - for (output <- children[DeclareOutput]) { - if (output.primary) { - throw XProcException.xsPrimaryOutputOnFinally(output.port, location) - } - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[TryCatchStart] - val node = start.addFinally(stepName, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - for (child <- allChildren) { - child.graphEdges(runtime, _graphNode.get) - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startFinally(tumble_id, stepName) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endFinally() - } - - override def toString: String = { - s"p:finally $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/ForContainer.scala b/src/main/scala/com/xmlcalabash/model/xml/ForContainer.scala deleted file mode 100644 index f0bc3a7..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/ForContainer.scala +++ /dev/null @@ -1,27 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash - -abstract class ForContainer(override val config: XMLCalabash) extends Container(config) { - - protected def setupLoopInputs(primary: Option[Boolean]): Unit = { - val first = firstChild - - if (primary.isDefined) { - if (firstWithInput.isEmpty) { - val input = new WithInput(config) - input.port = "source" - input.primary = primary.get - addChild(input, first) - } - } - - val current = new DeclareInput(config) - current.port = "current" - current.primary = true - addChild(current, first) - - makeContainerStructureExplicit() - } - -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/ForEach.scala b/src/main/scala/com/xmlcalabash/model/xml/ForEach.scala deleted file mode 100644 index 2d3e065..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/ForEach.scala +++ /dev/null @@ -1,84 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ChooseStart, ContainerStart, Node} -import com.jafpl.steps.Manifold -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.MediaType -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.XdmNode - -class ForEach(override val config: XMLCalabash) extends Container(config) with NamedArtifact { - override def parse(node: XdmNode): Unit = { - super.parse(node) - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - val fc = firstChild - if (firstWithInput.isEmpty) { - val winput = new WithInput(config) - winput.port = "source" - winput.primary = true - winput.sequence = true - addChild(winput, fc) - } else { - val wi = firstWithInput.get - wi.primary = true - wi.sequence = true - if (wi.port == "") { - wi.port = "source" - } - } - - val input = new DeclareInput(config) - input.port = "current" - input.primary = true - input.contentTypes = List(MediaType.OCTET_STREAM) - addChild(input, fc) - - makeContainerStructureExplicit() - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[ContainerStart] - val node = start.addForEach(stepName, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - val winput = firstWithInput - if (winput.isDefined) { - for (pipe <- winput.get.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, "source") - } - } - - for (output <- children[DeclareOutput]) { - for (pipe <- output.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, output.port) - } - } - - graphChildEdges(runtime) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startForEach(tumble_id, stepName) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endForEach() - } - - override def toString: String = { - s"p:for-each $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/ForLoop.scala b/src/main/scala/com/xmlcalabash/model/xml/ForLoop.scala deleted file mode 100644 index 4913379..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/ForLoop.scala +++ /dev/null @@ -1,92 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node} -import com.jafpl.steps.Manifold -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmNode} - -class ForLoop(override val config: XMLCalabash) extends ForContainer(config) with NamedArtifact { - private val _from = new QName("from") - private val _to = new QName("to") - private val _by = new QName("by") - private var countFrom = 1L - private var countTo = 0L - private var countBy = 1L - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(_from)) { - countFrom = attr(_from).get.toLong - } - - if (attributes.contains(_to)) { - countTo = attr(_to).get.toLong - } else { - throw new RuntimeException("to is required") - } - - if (attributes.contains(_by)) { - countBy = attr(_by).get.toLong - } - - if (countBy == 0) { - throw XProcException.xiAttempToCountByZero(location) - } - - if ((countFrom > countTo && countBy > 0) - || (countFrom < countTo && countBy < 0)) { - logger.debug(s"Counting from $countFrom to $countTo by $countBy will never execute") - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - setupLoopInputs(None) - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[ContainerStart] - val node = start.addFor(stepName, countFrom, countTo, countBy, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - val winput = firstWithInput - if (winput.isDefined) { - for (pipe <- winput.get.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, "source") - } - } - - for (output <- children[DeclareOutput]) { - for (pipe <- output.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, output.port) - } - } - - graphChildEdges(runtime) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startForLoop(tumble_id, stepName) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endForLoop() - } - - override def toString: String = { - s"cx:until $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/ForUntil.scala b/src/main/scala/com/xmlcalabash/model/xml/ForUntil.scala deleted file mode 100644 index 01dd7f8..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/ForUntil.scala +++ /dev/null @@ -1,140 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node} -import com.jafpl.steps.Manifold -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.XmlItemComparator -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmNode} - -class ForUntil(override val config: XMLCalabash) extends ForContainer(config) with NamedArtifact { - private val _max_iterations = new QName("max-iterations") - private val _comparator = new QName("comparator") - private val _return = new QName("return") - private var maxIterations: Long = -1 - private var comparator: String = "deep-equal($a,$b)" - private var returnSet: String = "last" - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(_max_iterations)) { - maxIterations = attr(_max_iterations).get.toString.toInt - } - - if (attributes.contains(_comparator)) { - comparator = attr(_comparator).get.toString - } - - if (attributes.contains(_return)) { - returnSet = attr(_return).get.toString - if (returnSet != "last" && returnSet != "all") { - throw new RuntimeException("return must be last or all") - } - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - setupLoopInputs(Some(true)) - - // Now let's consider what making the container structure explicit has done. - // If either the primary output or the test output haven't been specified, - // make sure it defaults to the primary output of the last step. - // Note: there will always be at least one! - var testOutput = Option.empty[DeclareOutput] - var resultOutput = Option.empty[DeclareOutput] - for (child <- children[DeclareOutput]) { - if (child.port == "test") { - testOutput = Some(child) - child.primary = false - } else { - if (child.primary) { - resultOutput = Some(child) - } - } - } - - if (resultOutput.isEmpty) { - setDefaultOutput("#result", true) - } - - if (testOutput.isEmpty) { - setDefaultOutput("test", false) - } - } - - private def setDefaultOutput(port: String, primary: Boolean): Unit = { - var lastStep = Option.empty[Step] - - for (child <- allChildren) { - child match { - case atomic: AtomicStep => lastStep = Some(atomic) - case compound: Container => lastStep = Some(compound) - case _ => () - } - } - - val output = new DeclareOutput(config) - output.port = port - output.primary = primary - output.sequence = true - - val pipe = new Pipe(config) - pipe.step = lastStep.get.stepName - pipe.port = lastStep.get.primaryOutput.get.port - pipe.link = lastStep.get.primaryOutput.get - output.addChild(pipe) - - if (firstChild.isDefined) { - addChild(output, firstChild.get) - } else { - addChild(output) - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val compare = new XmlItemComparator(config, comparator, maxIterations, this) - val start = parent.asInstanceOf[ContainerStart] - val node = start.addUntil(compare, returnSet=="all", stepName, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - val winput = firstWithInput - if (winput.isDefined) { - for (pipe <- winput.get.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, "source") - } - } - - for (output <- children[DeclareOutput]) { - for (pipe <- output.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, output.port) - } - } - - graphChildEdges(runtime) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startForUntil(tumble_id, stepName) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endForUntil() - } - - override def toString: String = { - s"cx:until $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/ForWhile.scala b/src/main/scala/com/xmlcalabash/model/xml/ForWhile.scala deleted file mode 100644 index 15ffd9f..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/ForWhile.scala +++ /dev/null @@ -1,142 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node} -import com.jafpl.steps.Manifold -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.XmlItemTester -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmNode} - -class ForWhile(override val config: XMLCalabash) extends ForContainer(config) with NamedArtifact { - private val _max_iterations = new QName("max-iterations") - private val _return = new QName("return") - private var maxIterations: Long = -1 - private var test: String = "" - private var returnSet: String = "last" - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(_max_iterations)) { - maxIterations = attr(_max_iterations).get.toInt - } - - if (attributes.contains(XProcConstants._test)) { - test = attr(XProcConstants._test).get - } else { - throw new RuntimeException("test is required") - } - - if (attributes.contains(_return)) { - returnSet = attr(_return).get - if (returnSet != "last" && returnSet != "all") { - throw new RuntimeException("return must be last or all") - } - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - setupLoopInputs(Some(true)) - - // Now let's consider what making the container structure explicit has done. - // If either the primary output or the test output haven't been specified, - // make sure it defaults to the primary output of the last step. - // Note: there will always be at least one! - var testOutput = Option.empty[DeclareOutput] - var resultOutput = Option.empty[DeclareOutput] - for (child <- children[DeclareOutput]) { - if (child.port == "test") { - testOutput = Some(child) - child.primary = false - } else { - if (child.primary) { - resultOutput = Some(child) - } - } - } - - if (resultOutput.isEmpty) { - setDefaultOutput("#result", true) - } - - if (testOutput.isEmpty) { - setDefaultOutput("test", false) - } - } - - private def setDefaultOutput(port: String, primary: Boolean): Unit = { - var lastStep = Option.empty[Step] - - for (child <- allChildren) { - child match { - case atomic: AtomicStep => lastStep = Some(atomic) - case compound: Container => lastStep = Some(compound) - case _ => () - } - } - - val output = new DeclareOutput(config) - output.port = port - output.primary = primary - output.sequence = true - - val pipe = new Pipe(config) - pipe.step = lastStep.get.stepName - pipe.port = lastStep.get.primaryOutput.get.port - pipe.link = lastStep.get.primaryOutput.get - output.addChild(pipe) - - if (firstChild.isDefined) { - addChild(output, firstChild.get) - } else { - addChild(output) - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val tester = new XmlItemTester(config, test, maxIterations, this) - val start = parent.asInstanceOf[ContainerStart] - val node = start.addWhile(tester, returnSet=="all", stepName, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - val winput = firstWithInput - if (winput.isDefined) { - for (pipe <- winput.get.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, "source") - } - } - - for (output <- children[DeclareOutput]) { - for (pipe <- output.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, output.port) - } - } - - graphChildEdges(runtime) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startForWhile(tumble_id, stepName) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endForWhile() - } - - override def toString: String = { - s"cx:while $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/Group.scala b/src/main/scala/com/xmlcalabash/model/xml/Group.scala deleted file mode 100644 index d1fd8bd..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Group.scala +++ /dev/null @@ -1,62 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node, TryCatchStart} -import com.jafpl.steps.Manifold -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.XdmNode - -class Group(override val config: XMLCalabash) extends Container(config) with NamedArtifact { - override def parse(node: XdmNode): Unit = { - super.parse(node) - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - makeContainerStructureExplicit() - } - - override def graphNodes(runtime: XMLCalabashRuntime, parNode: Node): Unit = { - if (parent.get.isInstanceOf[Try]) { - val start = parNode.asInstanceOf[TryCatchStart] - val node = start.addTry(stepName, containerManifold) - _graphNode = Some(node) - } else { - val start = parNode.asInstanceOf[ContainerStart] - - val node = start.addGroup(stepName, containerManifold) - _graphNode = Some(node) - } - - super.graphNodes(runtime, parNode) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - for (output <- children[DeclareOutput]) { - for (pipe <- output.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, output.port) - } - } - - graphChildEdges(runtime) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startGroup(tumble_id, stepName) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endGroup() - } - - override def toString: String = { - s"p:group $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/If.scala b/src/main/scala/com/xmlcalabash/model/xml/If.scala deleted file mode 100644 index b1661e3..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/If.scala +++ /dev/null @@ -1,44 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ChooseStart, ContainerStart, Node} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import net.sf.saxon.s9api.XdmNode - -import scala.collection.mutable - -class If(override val config: XMLCalabash) extends Choose(config) { - override protected[model] def makeStructureExplicit(): Unit = { - // p:if is purely syntactic sugar for a p:choose...so let's implement it that way - - val choose = new Choose(config) - choose._name = Some(stepName) - choose._depends = _depends - choose.p_if = true - for (child <- children[WithInput]) { - choose.addChild(child) - } - val when = new When(config) - when.test = ifexpr.get - if (ifcoll.isDefined) { - when.collection = ifcoll.get - } - for (child <- allChildren) { - child match { - case _: WithInput => () - case _ => - when.addChild(child) - } - } - choose.addChild(when) - parent.get.replaceChild(choose, this) - - choose.makeStructureExplicit() - } - - override def toString: String = { - s"p:choose $stepName (was p:if)" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/Import.scala b/src/main/scala/com/xmlcalabash/model/xml/Import.scala deleted file mode 100644 index 7c6c48b..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Import.scala +++ /dev/null @@ -1,77 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash - -import java.net.URI -import com.xmlcalabash.config.DocumentRequest -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.util.{MediaType, S9Api} -import net.sf.saxon.s9api.XdmNode - -class Import(override val config: XMLCalabash) extends Artifact(config) { - private var _href: URI = _ - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(XProcConstants._href)) { - _href = node.getBaseURI.resolve(attr(XProcConstants._href).get) - } else { - throw XProcException.xsMissingRequiredAttribute(XProcConstants._href, location) - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - protected[model] def loadImports(): DeclContainer = { - if (config.importedURI(_href).isEmpty) { - var root: XdmNode = null - try { - val request = new DocumentRequest(_href, MediaType.XML) - val response = config.documentManager.parse(request) - root = S9Api.documentElement(response.value.asInstanceOf[XdmNode]).get - } catch { - case ex: XProcException => - if (ex.code == XProcException.err_xd0011) { - throw XProcException.xsImportFailed(_href, location) - } else { - throw ex - } - } - - val parser = new Parser(config) - - val declContainer = root.getNodeName match { - case XProcConstants.p_declare_step => - parser.parseDeclareStep(root) - case XProcConstants.p_library => - parser.parseLibrary(root) - case _ => - throw XProcException.xsBadImport(root.getNodeName, location) - } - - declContainer match { - case decl: DeclareStep => - if (decl.stepType.isEmpty) { - throw XProcException.xsStepTypeRequired(location) - } - case _ => () - } - - config.addImportedURI(_href, declContainer) - - declContainer.loadImports() - declContainer - } else { - config.importedURI(_href).get - } - } - - override def toString: String = { - s"p:import $tumble_id ${_href}" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/ImportFunctions.scala b/src/main/scala/com/xmlcalabash/model/xml/ImportFunctions.scala deleted file mode 100644 index 41f2561..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/ImportFunctions.scala +++ /dev/null @@ -1,31 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash - -import java.net.URI -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import net.sf.saxon.s9api.XdmNode - -class ImportFunctions(override val config: XMLCalabash) extends Artifact(config) { - private var _href: URI = _ - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(XProcConstants._href)) { - _href = node.getBaseURI.resolve(attr(XProcConstants._href).get) - } else { - throw XProcException.xsMissingRequiredAttribute(XProcConstants._href, location) - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override def toString: String = { - s"p:import-functions $tumble_id ${_href}" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Inline.scala b/src/main/scala/com/xmlcalabash/model/xml/Inline.scala deleted file mode 100644 index bbedb8f..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Inline.scala +++ /dev/null @@ -1,287 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.messages.Message -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} -import com.xmlcalabash.runtime.StaticContext -import com.xmlcalabash.runtime.params.InlineLoaderParams -import com.xmlcalabash.util.xc.ElaboratedPipeline -import com.xmlcalabash.util.{MediaType, S9Api} -import net.sf.saxon.s9api.{Axis, QName, XdmNode, XdmNodeKind} - -import java.io.UnsupportedEncodingException -import java.util.Base64 -import scala.collection.mutable - -class Inline(override val config: XMLCalabash, srcNode: XdmNode, implied: Boolean) extends DataSource(config) { - private var _node: XdmNode = srcNode - private var _contentType = Option.empty[MediaType] - private var _documentProperties = Option.empty[String] - private var _encoding = Option.empty[String] - private var _exclude_inline_prefixes = Option.empty[String] - private var _context_provided = false - private val nameBindings = mutable.HashSet.empty[QName] - private val _statics = mutable.HashMap.empty[String, Message] - private var _expandText = false - - if (srcNode.getNodeKind != XdmNodeKind.DOCUMENT) { - throw XProcException.xiThisCantHappen(s"Attempt to create p:inline from something that isn't a document node: ${srcNode}") - } - - def this(config: XMLCalabash, srcNode: XdmNode) = { - this(config, srcNode, false) - } - - def this(copy: Inline) = { - this(copy.config, copy._node, true) // Why can't I put copy._synthetic in the constructor? - _synthetic = copy._synthetic - depends ++= copy.depends - _contentType = copy._contentType - _documentProperties = copy._documentProperties - _encoding = copy._encoding - _exclude_inline_prefixes = copy._exclude_inline_prefixes - _context_provided = copy._context_provided - nameBindings ++= copy.nameBindings - _inScopeDynamics = copy._inScopeDynamics - _inScopeStatics = copy._inScopeStatics - _expandText = copy._expandText - staticContext = copy.staticContext - } - - def node: XdmNode = _node - def contentType: Option[MediaType] = _contentType - def documentProperties: Option[String] = _documentProperties - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - _synthetic = implied - - if (node.getNodeName == XProcConstants.p_inline) { - _contentType = MediaType.parse(attributes.get(XProcConstants._content_type)) - if (_contentType.isDefined) { - _contentType.get.assertValid - } - _documentProperties = attr(XProcConstants._document_properties) - _encoding = attr(XProcConstants._encoding) - _exclude_inline_prefixes = attr(XProcConstants._exclude_inline_prefixes) - - if (_contentType.isDefined && _encoding.isEmpty) { - if (_contentType.get.charset.isDefined) { - throw XProcException.xdCharsetWithoutEncoding(attributes(XProcConstants._content_type), location) - } - } - - if (_encoding.isDefined) { - if (_encoding.get == "base64") { - if (_contentType.isDefined && _contentType.get.markupContentType) { - throw XProcException.xdCannotEncodeMarkup(_encoding.get, _contentType.get, location) - } - - val charset = if (_contentType.isDefined) { - _contentType.get.charset.getOrElse("UTF-8") - } else { - "UTF-8" - } - - // Can I trust you? - try { - // Apparently the decode won't acept newlines in the data... - val str = srcNode.getStringValue.trim.replace("\n", "") - val bytes = Base64.getDecoder.decode(str) - if (contentType.isDefined && contentType.get.textContentType) { - new String(bytes, charset) - } - } catch { - case ex: IllegalArgumentException => - throw XProcException.xdIncorrectEncoding(_encoding.get, location) - case _: UnsupportedEncodingException => - throw XProcException.xdUnsupportedCharset(charset, location) - case ex: Exception => - throw ex - } - } else { - throw XProcException.xsUnsupportedEncoding(_encoding.get, location) - } - } - - if (_contentType.isEmpty) { - _contentType = Some(MediaType.XML) - } - - _expandText = inScopeExpandText(node) - } - } - - def expandText: Boolean = _expandText - def encoding: Option[String] = _encoding - - def excludeURIs: Set[String] = { - if (_exclude_inline_prefixes.isDefined) { - S9Api.urisForPrefixes(node, _exclude_inline_prefixes.get.split("\\s+").toSet) - } else { - Set() - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - val env = environment() - val drp = env.defaultReadablePort - - if (allChildren.isEmpty && drp.isDefined && !parent.get.isInstanceOf[DeclareInput]) { - _context_provided = true - val pipe = new Pipe(config) - pipe.port = drp.get.port - pipe.step = drp.get.step.stepName - pipe.link = drp.get - addChild(pipe) - } - - val ctype = if (_contentType.isDefined) { - _contentType.get - } else { - MediaType.XML - } - - // Is this sufficient? We don't want to attempt to parse AVTs in JSON! - if (expand_text && (ctype.markupContentType || ctype.textContentType)) { - findVariablesInTVT(_node, expand_text) - for (ref <- nameBindings) { - val binding = env.variable(ref) - if (binding.isEmpty) { - throw XProcException.xsNoBindingInExpression(ref, location) - } - if (binding.get.static) { - _statics.put(ref.getClarkName, binding.get.staticValue.get) - } else { - val pipe = new NamePipe(config, ref, binding.get.tumble_id, binding.get) - addChild(pipe) - } - } - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - // Trim the leading and trailing whitespace - if (synthetic) { - var children = S9Api.axis(srcNode, Axis.CHILD) - if (children.nonEmpty && children.head.getNodeKind == XdmNodeKind.TEXT && children.head.getStringValue.trim == "") { - children = children.tail - } - if (children.nonEmpty && children.last.getNodeKind == XdmNodeKind.TEXT && children.last.getStringValue.trim == "") { - children = children.dropRight(1) - } - val tree = new SaxonTreeBuilder(config) - tree.startDocument(srcNode.getBaseURI) - for (child <- children) { - tree.addSubtree(child) - } - tree.endDocument() - _node = tree.result - _contentType = Some(MediaType.XML) - } - } - - override protected[model] def validateStructure(): Unit = { - // nop - } - - def inlineContext: StaticContext = { - staticContext.withStatics(inScopeStatics) - } - - override protected[model] def normalizeToPipes(): Unit = { - val params = new InlineLoaderParams(_node, _contentType, _documentProperties, _encoding, _exclude_inline_prefixes, expand_text, _context_provided, inlineContext) - normalizeDataSourceToPipes(XProcConstants.cx_inline_loader, params) - } - - private def findVariablesInTVT(node: XdmNode, expandText: Boolean): Unit = { - node.getNodeKind match { - case XdmNodeKind.DOCUMENT => - val iter = node.axisIterator(Axis.CHILD) - while (iter.hasNext) { - val child = iter.next() - findVariablesInTVT(child, expandText) - } - case XdmNodeKind.ELEMENT => - var newExpand = expandText - var iter = node.axisIterator(Axis.ATTRIBUTE) - while (iter.hasNext) { - var discardAttribute = false - val attr = iter.next() - if (attr.getNodeName == XProcConstants.p_inline_expand_text) { - if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { - throw XProcException.xsInlineExpandTextNotAllowed(location) - } - discardAttribute = true - newExpand = attr.getStringValue == "true" - } - if (attr.getNodeName == XProcConstants._inline_expand_text) { - if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { - discardAttribute = true - newExpand = attr.getStringValue == "true" - } - } - if (!discardAttribute) { - if (expandText) { - findVariablesInString(attr.getStringValue) - } - } - } - iter = node.axisIterator(Axis.CHILD) - while (iter.hasNext) { - val child = iter.next() - findVariablesInTVT(child, newExpand) - } - case XdmNodeKind.TEXT => - val str = node.getStringValue - if (expandText && str.contains("{")) { - findVariablesInNodes(str) - } - case _ => () - } - } - - private def findVariablesInString(text: String): Unit = { - val expr = staticContext.parseAvt(text) - for (name <- ValueParser.findVariableRefsInAvt(config, expr)) { - nameBindings += name - } - } - - private def findVariablesInNodes(text: String): Unit = { - val expr = staticContext.parseAvt(text) - for (name <- ValueParser.findVariableRefsInAvt(config, expr)) { - nameBindings += name - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - var root = Option.empty[QName] - val iter = _node.axisIterator(Axis.CHILD) - while (root.isEmpty && iter.hasNext) { - val item = iter.next() - if (item.getNodeKind == XdmNodeKind.ELEMENT) { - root = Some(item.getNodeName) - } - } - - xml.startInline(tumble_id, root) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endInline() - } - - override def toString: String = { - val root = S9Api.documentElement(_node) - if (root.isDefined) { - s"p:inline <${root.get.getNodeName}> $tumble_id" - } else { - s"p:inline <> $tumble_id" - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Library.scala b/src/main/scala/com/xmlcalabash/model/xml/Library.scala deleted file mode 100644 index a2fa0db..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Library.scala +++ /dev/null @@ -1,82 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import net.sf.saxon.s9api.{XdmNode, XdmNodeKind} - -class Library(override val config: XMLCalabash) extends DeclContainer(config) { - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(XProcConstants._version)) { - val vstr = attr(XProcConstants._version).get - try { - _version = Some(vstr.toDouble) - } catch { - case ex: NumberFormatException => - throw XProcException.xsBadVersion(vstr, location) - } - if (_version.get != 3.0) { - throw XProcException.xsInvalidVersion(_version.get, location) - } - } - if (_version.isEmpty) { - if (node.getParent.getNodeKind == XdmNodeKind.DOCUMENT) { - throw XProcException.xsVersionRequired(location) - } - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - for (child <- allChildren) { - child match { - case decl: DeclareStep => - decl.makeStructureExplicit() - case variable: Variable => - variable.makeStructureExplicit() - case function: DeclareFunction => - function.makeStructureExplicit() - case _ => - throw new RuntimeException(s"Invalid element: $child") - } - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - for (child <- allChildren) { - child match { - case decl: DeclareStep => - decl.makeBindingsExplicit() - case variable: Variable => - if (!variable.static) { - throw new RuntimeException("Variables in libraries must be static") - } - variable.makeBindingsExplicit() - case function: DeclareFunction => - () - } - } - } - - override protected[model] def validateStructure(): Unit = { - for (child <- allChildren) { - child.validateStructure() - child match { - case variable: Variable => - if (!variable.static) { - throw new RuntimeException("Only static variables are allowed in a p:library") - } - case step: DeclareStep => () - case function: DeclareFunction => () - case _ => - throw new RuntimeException(s"Child not allowed: $child") - } - } - } - - override def toString: String = { - s"p:library $location $tumble_id" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/NameBinding.scala b/src/main/scala/com/xmlcalabash/model/xml/NameBinding.scala deleted file mode 100644 index e49a634..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/NameBinding.scala +++ /dev/null @@ -1,417 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.jafpl.messages.Message -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.config.DocumentRequest -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.messages.{XdmNodeItemMessage, XdmValueItemMessage} -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.{XMLCalabashRuntime, XProcMetadata, XProcVtExpression, XProcXPathExpression} -import com.xmlcalabash.util.{TvtExpander, TypeUtils} -import net.sf.saxon.ma.map.MapType -import net.sf.saxon.om.StructuredQName -import net.sf.saxon.s9api.{QName, SaxonApiException, SequenceType, XdmAtomicValue, XdmNode} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -class NameBinding(override val config: XMLCalabash) extends Artifact(config) { - protected var _name: QName = _ - protected var _declaredType = Option.empty[SequenceType] - protected var _as = Option.empty[SequenceType] - protected var _values = Option.empty[String] - protected var _static = Option.empty[Boolean] - protected var _required = Option.empty[Boolean] - protected var _select = Option.empty[String] - protected var _avt = Option.empty[String] - protected var _visibility = Option.empty[String] - protected var _allowedValues = Option.empty[List[XdmAtomicValue]] - protected var _staticValue = Option.empty[XdmValueItemMessage] - protected var _dependentNameBindings: ListBuffer[NamePipe] = ListBuffer.empty[NamePipe] - protected var collection = false - - private var _qnameKeys = false - private var resolvedStatically = false - private val structuredQName = new StructuredQName("xs", XProcConstants.ns_xs, "QName") - protected val depends: ListBuffer[String] = ListBuffer.empty[String] - - protected var _href = Option.empty[String] - protected var _pipe = Option.empty[String] - - private var typeUtils: TypeUtils = _ - - def name: QName = _name - def as: Option[SequenceType] = _as - protected[model] def as_=(seq: SequenceType): Unit = { - _as = Some(seq) - } - - def declaredType: SequenceType = { - if (_declaredType.isEmpty) { - _declaredType = staticContext.parseSequenceType(Some("xs:string")) - } - _declaredType.get - } - protected[model] def declaredType_=(decltype: SequenceType): Unit = { - _declaredType = Some(decltype) - } - - def values: Option[String] = _values - def required: Boolean = _required.getOrElse(false) - def select: Option[String] = _select - protected[model] def select_=(select: String): Unit = { - _select = Some(select) - } - def avt: Option[String] = _avt - protected[model] def avt_=(expr: String): Unit = { - if (select.isDefined) { - throw new RuntimeException("Cannot define AVT if select is present") - } - _avt = Some(expr) - } - - def static: Boolean = _static.getOrElse(false) - def visibility: String = _visibility.getOrElse("public") - def allowedValues: Option[List[XdmAtomicValue]] = _allowedValues - def qnameKeys: Boolean = _qnameKeys - protected[xml] def qnameKeys_=(keys: Boolean): Unit = { - _qnameKeys = keys - } - - protected[xmlcalabash] def staticValue: Option[XdmValueItemMessage] = _staticValue - protected[model] def staticValue_=(value: XdmValueItemMessage): Unit = { - _staticValue = Some(value) - } - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - typeUtils = new TypeUtils(config, staticContext) - - if (attributes.contains(XProcConstants._name)) { - val name = attr(XProcConstants._name).get - try { - _name = staticContext.parseQName(name) - } catch { - case ex: XProcException => - if (ex.code == XProcException.err_xd0015) { - throw XProcException.xsOptionUndeclaredNamespace(name, ex.location) - } else { - throw ex - } - case ex: Throwable => - throw ex - } - this match { - case _: WithOption => - () // This would be ok if the step has an option declared in the p: namespace - case _ => - if (_name.getNamespaceURI == XProcConstants.ns_p) { - throw XProcException.xsOptionInXProcNamespace(_name, location) - } - } - } else { - throw XProcException.xsMissingRequiredAttribute(XProcConstants._name, location) - } - - val seqTypeString = attr(XProcConstants._as) - _as = typeUtils.parseSequenceType(seqTypeString) - - if (as.isDefined) - as.get.getUnderlyingSequenceType.getPrimaryType match { - case map: MapType => - if (map.getKeyType.getPrimitiveItemType.getTypeName == structuredQName) { - // We have to lie about the type of maps with QName keys because we're - // going to allow users to put strings in there. - _qnameKeys = true - _as = Some(typeUtils.parseFakeMapSequenceType(seqTypeString.get)) - } - case _ => () - } - - _values = attr(XProcConstants._values) - - if (_values.isDefined) { - val exprEval = config.expressionEvaluator.newInstance() - val expr = new XProcXPathExpression(staticContext, _values.get, None, None, None) - val value = exprEval.value(expr, List(), Map(), None) - val iter = value.item.iterator() - val allowed = ListBuffer.empty[XdmAtomicValue] - while (iter.hasNext) { - val token = iter.next() - token match { - case atom: XdmAtomicValue => - allowed += atom - case _ => - throw XProcException.xsInvalidValues(_values.get, location) - } - } - _allowedValues = Some(allowed.toList) - } - - _static = staticContext.parseBoolean(attr(XProcConstants._static)) - _required = staticContext.parseBoolean(attr(XProcConstants._required)) - _select = attr(XProcConstants._select) - _visibility = attr(XProcConstants._visibility) - - if (_required.isDefined && _required.get && _select.isDefined) { - throw XProcException.xsRequiredAndDefaulted(_name, location) - } - - val _collection = attr(XProcConstants._collection) - if (_collection.isDefined) { - val coll = _collection.get - if (List("1", "true", "yes").contains(coll)) { - collection = true - } else { - if (List("0", "false", "no").contains(coll)) { - collection = false - } else { - throw XProcException.xsBadTypeValue(coll, "xs:boolean", location) - } - } - } - - _href = attr(XProcConstants._href) - _pipe = attr(XProcConstants._pipe) - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - if (_href.isDefined && _pipe.isDefined) { - throw XProcException.xsPipeAndHref(location) - } - - if (_href.isDefined && allChildren.nonEmpty) { - throw XProcException.xsHrefAndOtherSources(location) - } - - if (_pipe.isDefined && allChildren.nonEmpty) { - throw XProcException.xsPipeAndOtherSources(location) - } - - if (_href.isDefined) { - val doc = new Document(config) - doc.href = _href.get - addChild(doc) - } - - if (_pipe.isDefined) { - var port = Option.empty[String] - var step = Option.empty[String] - if (_pipe.get.contains("@")) { - val re = "(.*)@(.*)".r - _pipe.get match { - case re(pname, sname) => - if (pname != "") { - port = Some(pname) - } - step = Some(sname) - } - } else { - if (_pipe.get.trim() != "") { - port = _pipe - } - } - - val pipe = new Pipe(config) - if (step.isDefined) { - pipe.step = step.get - } - if (port.isDefined) { - pipe.port = port.get - } - addChild(pipe) - } - - for (child <- allChildren) { - child.makeStructureExplicit() - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - // If the ancestor of a data source has a dependency, so does the data source - var p = parent - while (p.isDefined) { - p.get match { - case step: Step => - for (name <- step.depends) { - if (!depends.contains(name)) { - depends += name - } - } - case _ => - () - } - p = p.get.parent - } - - var exprString = "" - val usesContextItem = try { - if (_avt.isDefined) { - exprString = _avt.toString - val avt = staticContext.parseAvt(_avt.get) - staticContext.dependsOnContextAvt(avt) - } else { - exprString = _select.getOrElse("()") - staticContext.dependsOnContextString(_select.getOrElse("()")) - } - } catch { - case ex: Throwable => - throw XProcException.xsStaticErrorInExpression(exprString, ex.getMessage, location) - } - - val ds = ListBuffer.empty[DataSource] - for (child <- allChildren) { - child match { - case pipe: Pipe => - if (static) { - throw XProcException.xsStaticRefsContext("Static variables cannot refer to the context.", location) - } - ds += pipe - case source: DataSource => - ds += source - case _ => - throw new RuntimeException(s"Unexpected child: $child") - } - } - - val env = environment() - val drp = env.defaultReadablePort - - if (ds.isEmpty) { - if (drp.isDefined && !static && usesContextItem) { - val winput = new WithInput(config) - winput.port = "source" - addChild(winput) - val pipe = new Pipe(config) - pipe.port = drp.get.port - pipe.step = drp.get.step.stepName - pipe.link = drp.get - winput.addChild(pipe) - } - } else { - removeChildren() - val winput = new WithInput(config) - winput.port = "source" - addChild(winput) - for (source <- ds) { - winput.addChild(source) - } - } - - if (static) { - val context = ListBuffer.empty[Message] - for (child <- ds) { - child match { - case inline: Inline => - val exprContext = staticContext.withStatics(inScopeStatics) - val expander = new TvtExpander(config, None, exprContext, Map(), location) - - try { - // FIXME: what should the defaults for initiallyExpand and exludeURIs be? - val result = expander.expand(inline.node, true, Set()) - context += new XdmNodeItemMessage(result, XProcMetadata.XML, inline.staticContext) - } catch { - case ex: XProcException => - if (ex.code == XProcException.err_xs0107 && ex.details(1).toString.contains("Undeclared variable")) { - throw XProcException.xsStaticRefsNonStaticStr(ex.details.head.toString, location) - } - throw ex - } - - case doc: Document => - for (ref <- staticContext.findVariableRefsInAvt(doc.hrefAvt)) { - if (!inScopeStatics.contains(ref.getClarkName)) { - throw XProcException.xsStaticRefsNonStatic(ref, location) - } - } - val econtext = staticContext.withStatics(inScopeStatics) - val exprEval = config.expressionEvaluator.newInstance() - val vtexpr = new XProcVtExpression(econtext, doc.hrefAvt) - val value = exprEval.singletonValue(vtexpr, List(), inScopeStatics, None) - val parts = ListBuffer.empty[String] - val viter = value.item.iterator() - while (viter.hasNext) { - parts += viter.next().getStringValue - } - - val href = staticContext.baseURI.get.resolve(parts.mkString("")) - - val request = new DocumentRequest(href, None, location) - val response = config.documentManager.parse(request) - context += new XdmValueItemMessage(response.value, XProcMetadata.XML, doc.staticContext) - case empty: Empty => - () - } - } - - val expr = new XProcXPathExpression(staticContext, select.get) - val exeval = config.expressionEvaluator.newInstance() - val msg = exeval.value(expr, context.toList, inScopeStatics, None) - staticValue = msg - resolvedStatically = true - } - - if (_select.isDefined) { - val bindings = mutable.HashSet.empty[QName] - bindings ++= staticContext.findVariableRefsInString(_select.get) - if (bindings.isEmpty) { - try { - val depends = staticContext.dependsOnContextString(_select.get) - // FIXME: if depends is false, we can resolve this statically - } catch { - case sax: SaxonApiException => () - } - } else { - for (ref <- bindings) { - val binding = env.variable(ref) - if (binding.isEmpty) { - throw XProcException.xsNoBindingInExpression(ref, location); - } - if (!binding.get.static) { - val pipe = new NamePipe(config, ref, binding.get.tumble_id, binding.get) - _dependentNameBindings += pipe - addChild(pipe) - } - } - } - } - } - - override protected[model] def validateStructure(): Unit = { - var hasEmpty = false - var hasNonEmpty = false - - for (child <- allChildren) { - child.validateStructure() - child match { - case winput: WithInput => () - case npipe: NamePipe => () - case _ => - throw new RuntimeException(s"Invalid content in $this") - } - } - - if (hasEmpty && hasNonEmpty) { - throw XProcException.xsNoSiblingsOnEmpty(location) - } - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - if (resolvedStatically) { - return - } - - for (child <- allChildren) { - child.graphEdges(runtime, _graphNode.get) - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/NamePipe.scala b/src/main/scala/com/xmlcalabash/model/xml/NamePipe.scala deleted file mode 100644 index eea0ef4..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/NamePipe.scala +++ /dev/null @@ -1,57 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmNode} - -class NamePipe(override val config: XMLCalabash, val name: QName, val step: String, val link: NameBinding) extends Artifact(config) { - private var _node = link._graphNode - - override def parse(node: XdmNode): Unit = { - throw new RuntimeException("This is a purely synthetic element") - } - - override protected[model] def validateStructure(): Unit = { - if (allChildren.nonEmpty) { - throw new RuntimeException(s"Invalid content in $this") - } - } - - // Name pipes for options that depend on the values of preceding options in the same - // declaration get constructed before the corresponding with-option nodes for evaluating - // them. This patch hack let's the node be updated later. - protected[model] def patchNode(node: Node): Unit = { - _node = Some(node) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parNode: Node): Unit = { - if (_node.isEmpty) { - _node = link._graphNode - } - - if (_node.isEmpty) { - throw XProcException.xiThisCantHappen(s"Attempt to link from non-existant graph node for ${link}", None) - } - - val toNode = parNode - val toPort = "#bindings" - val fromNode = _node.get - val fromPort = "result" - runtime.graph.addEdge(fromNode, fromPort, toNode, toPort) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startNamePipe(tumble_id, step) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endNamePipe() - } - - override def toString: String = { - s"p:name-pipe $name $step" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/NamedArtifact.scala b/src/main/scala/com/xmlcalabash/model/xml/NamedArtifact.scala deleted file mode 100644 index 23e6a96..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/NamedArtifact.scala +++ /dev/null @@ -1,5 +0,0 @@ -package com.xmlcalabash.model.xml - -trait NamedArtifact { - def stepName: String -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Otherwise.scala b/src/main/scala/com/xmlcalabash/model/xml/Otherwise.scala deleted file mode 100644 index f000a99..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Otherwise.scala +++ /dev/null @@ -1,59 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.XProcXPathExpression -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.XdmNode - -class Otherwise(override val config: XMLCalabash) extends ChooseBranch(config) { - - // An otherwise is not atomic, even if it contains only synthetic children - override def atomic: Boolean = false - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - _collection = false - _test = "true()" - testExpr = new XProcXPathExpression(staticContext, _test) - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - if (synthetic && environment().defaultReadablePort.isEmpty) { - val ident = children[Step].head - val winput = ident.firstWithInput - val empty = new Empty(config) - winput.get.addChild(empty) - } - - super.makeBindingsExplicit() - } - - override protected[model] def normalizeToPipes(): Unit = { - super.normalizeToPipes() - - // There's never any need for a with-input on p:otherwise - val winput = firstWithInput - if (winput.isDefined) { - removeChild(winput.get) - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startWhen(tumble_id, stepName, "true()") - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endWhen() - } - - override def toString: String = { - s"p:otherwise $stepName" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Parser.scala b/src/main/scala/com/xmlcalabash/model/xml/Parser.scala deleted file mode 100644 index 64beb4c..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Parser.scala +++ /dev/null @@ -1,614 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.config.DocumentRequest -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} -import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcLocation, XProcXPathExpression} -import com.xmlcalabash.util.{MediaType, S9Api} -import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap, FingerprintedQName} -import net.sf.saxon.s9api.{Axis, QName, XdmNode, XdmNodeKind} -import org.xml.sax.InputSource - -import java.net.URI -import javax.xml.transform.sax.SAXSource -import scala.collection.mutable.ListBuffer - -class Parser(config: XMLCalabash) { - private var _builtInSteps = config.builtinSteps - private var matcher: ProcessMatch = _ - - // Someone has to load the default steps; feels like here's as good a place as any. - // It doesn't feel like something the user should have to do explicitly. - if (_builtInSteps.isEmpty) { - config.builtinSteps = init_builtins() - _builtInSteps = config.builtinSteps - } - - def builtInSteps: List[Library] = _builtInSteps - - def loadLibrary(root: XdmNode): Library = { - val node = stripUseWhen(root) - if (node.getNodeKind != XdmNodeKind.ELEMENT || node.getNodeName != XProcConstants.p_library) { - throw new RuntimeException(s"Not a library: ${root.getNodeName}") - } - - parseLibrary(node) - } - - def loadDeclareStep(uri: URI): DeclareStep = { - val request = new DocumentRequest(uri, MediaType.XML) - val response = config.documentManager.parse(request) - loadDeclareStep(response.value.asInstanceOf[XdmNode]) - } - - def loadDeclareStep(root: XdmNode): DeclareStep = { - val node = stripUseWhen(root) - if (node.getNodeKind != XdmNodeKind.ELEMENT || node.getNodeName != XProcConstants.p_declare_step) { - throw new RuntimeException(s"Not a declare-step: ${root.getNodeName}") - } - - val decl = parseDeclareStep(node) - - if (node.getParent.getNodeKind == XdmNodeKind.DOCUMENT) { - decl.loadImports() - decl.updateInScopeSteps() - decl.parseDeclarationSignature() - - val toElaborate = ListBuffer.empty[DeclContainer] - for (lib <- config.builtinSteps) { - toElaborate += lib - } - for (uri <- config.importedURIs) { - toElaborate += config.importedURI(uri).get - } - toElaborate += decl - - for (root <- toElaborate) { - root.makeStructureExplicit() - root.makeBindingsExplicit() - root.validateStructure() - } - } - - config.clearImportedURIs() - - decl - } - - private def stripUseWhen(root: XdmNode): XdmNode = { - val staticContext = new StaticContext(config, None) - matcher = new ProcessMatch(config, new ProcessUseWhen(staticContext), staticContext) - matcher.process(root, "*") - val node = matcher.result - if (node.getNodeKind == XdmNodeKind.DOCUMENT) { - S9Api.documentElement(node).get - } else { - node - } - } - - private def parseContainer[T <: Container](node: XdmNode, container: T): T = { - container match { - case _: DeclareStep => - parseContainer(node, container, List(), List(XProcConstants.p_with_input)) - case _: If => - parseContainer(node, container, List(), List(XProcConstants.p_input, XProcConstants.p_declare_step, - XProcConstants.p_import, XProcConstants.p_import_functions)) - case _: Choose => - parseContainer(node, container, List(XProcConstants.p_with_input, XProcConstants.p_when, XProcConstants.p_otherwise), List()) - case _ => - parseContainer(node, container, List(), List(XProcConstants.p_input, XProcConstants.p_declare_step, - XProcConstants.p_import, XProcConstants.p_import_functions)) - } - } - - private def parseContainer[T <: Container](node: XdmNode, container: T, allowed: List[QName], forbidden: List[QName]): T = { - container.parse(node) - - for (child <- children(node)) { - child.getNodeKind match { - case XdmNodeKind.ELEMENT => - if (allowed.nonEmpty && !allowed.contains(child.getNodeName)) { - throw new RuntimeException(s"Not allowed here: ${child.getNodeName}") - } - if (forbidden.nonEmpty && forbidden.contains(child.getNodeName)) { - throw new RuntimeException(s"Not allowed here: ${child.getNodeName}") - } - - child.getNodeName match { - case XProcConstants.p_input => - container.addChild(parseInput(child)) - case XProcConstants.p_with_input => - container.addChild(parseWithInput(child)) - case XProcConstants.p_output => - container.addChild(parseOutput(child)) - case XProcConstants.p_option => - container.addChild(parseOption(child)) - case XProcConstants.p_variable => - container.addChild(parseVariable(child)) - case XProcConstants.p_declare_step => - container.addChild(parseDeclareStep(child)) - case XProcConstants.p_choose => - container.addChild(parseChoose(child)) - case XProcConstants.p_when => - container.addChild(parseWhen(child)) - case XProcConstants.p_otherwise => - container.addChild(parseOtherwise(child)) - case XProcConstants.p_if => - container.addChild(parseIf(child)) - case XProcConstants.p_for_each => - container.addChild(parseForEach(child)) - case XProcConstants.cx_until => - container.addChild(parseUntil(child)) - case XProcConstants.cx_while => - container.addChild(parseWhile(child)) - case XProcConstants.cx_loop => - container.addChild(parseLoop(child)) - case XProcConstants.p_viewport => - container.addChild(parseViewport(child)) - case XProcConstants.p_group => - container.addChild(parseGroup(child)) - case XProcConstants.p_try => - container.addChild(parseTry(child)) - case XProcConstants.p_catch => - container.addChild(parseCatch(child)) - case XProcConstants.p_finally => - container.addChild(parseFinally(child)) - case XProcConstants.p_import => - container.addChild(parseImport(child)) - case XProcConstants.p_import_functions => - container.addChild(parseImportFunctions(child)) - case XProcConstants.p_documentation => - container.addChild(parseDocumentation(child)) - case XProcConstants.p_pipeinfo => - container.addChild(parsePipeInfo(child)) - case _ => - // If we don't recognize it, assume it's an atomic step - container.addChild(parseAtomicStep(child)) - } - case XdmNodeKind.COMMENT => () - case XdmNodeKind.PROCESSING_INSTRUCTION => () - case XdmNodeKind.TEXT => - throw XProcException.xsTextNotAllowed(child.getStringValue.trim(), Some(new XProcLocation(child))) - case _ => - throw new RuntimeException(s"Unexpected element kind: ${child.getNodeKind}") - } - } - - container - } - - protected[model] def parseLibrary(node: XdmNode): Library = { - val library = new Library(config) - library.parse(node) - - for (child <- children(node)) { - child.getNodeKind match { - case XdmNodeKind.ELEMENT => - child.getNodeName match { - case XProcConstants.p_declare_step => - library.addChild(parseDeclareStep(child)) - case XProcConstants.p_variable => - library.addChild(parseVariable(child)) - case XProcConstants.p_function => - library.addChild(parseFunction(child)) - case XProcConstants.p_import => - library.addChild(parseImport(child)) - case XProcConstants.p_import_functions => - library.addChild(parseImportFunctions(child)) - case XProcConstants.p_documentation => - library.addChild(parseDocumentation(child)) - case XProcConstants.p_pipeinfo => - library.addChild(parsePipeInfo(child)) - case _ => - throw new RuntimeException(s"Unexpected element: ${child.getNodeName}") - } - case XdmNodeKind.COMMENT => () - case XdmNodeKind.PROCESSING_INSTRUCTION => () - case XdmNodeKind.TEXT => - throw XProcException.xsTextNotAllowed(child.getStringValue.trim(), Some(new XProcLocation(child))) - case _ => - throw new RuntimeException(s"Unexpected element kind: ${child.getNodeKind}") - } - } - - library - } - - protected[model] def parseDeclareStep(node: XdmNode): DeclareStep = { - val decl = new DeclareStep(config) - parseContainer(node, decl) - decl - } - - private def parseImport(node: XdmNode): Import = { - parseNoChildrenAllowed(node, new Import(config)) - } - - private def parseImportFunctions(node: XdmNode): ImportFunctions = { - parseNoChildrenAllowed(node, new ImportFunctions(config)) - } - - private def parseChoose(node: XdmNode): Choose = { - parseContainer(node, new Choose(config)) - } - - private def parseWhen(node: XdmNode): When = { - parseContainer(node, new When(config)) - } - - private def parseOtherwise(node: XdmNode): Otherwise = { - parseContainer(node, new Otherwise(config)) - } - - private def parseIf(node: XdmNode): If = { - parseContainer(node, new If(config)) - } - - private def parseForEach(node: XdmNode): ForEach = { - parseContainer(node, new ForEach(config)) - } - - private def parseUntil(node: XdmNode): ForUntil = { - parseContainer(node, new ForUntil(config)) - } - - private def parseWhile(node: XdmNode): ForWhile = { - parseContainer(node, new ForWhile(config)) - } - - private def parseLoop(node: XdmNode): ForLoop = { - parseContainer(node, new ForLoop(config)) - } - - private def parseViewport(node: XdmNode): Viewport = { - parseContainer(node, new Viewport(config)) - } - - private def parseGroup(node: XdmNode): Group = { - parseContainer(node, new Group(config)) - } - - private def parseTry(node: XdmNode): Try = { - parseContainer(node, new Try(config)) - } - - private def parseCatch(node: XdmNode): Catch = { - parseContainer(node, new Catch(config)) - } - - private def parseFinally(node: XdmNode): Finally = { - parseContainer(node, new Finally(config)) - } - - private def parseConnections[T <: Artifact](node: XdmNode, art: T): T = { - art match { - case input: DeclareInput => - parseConnections(node, art, List(XProcConstants.p_pipe)) - case _ => - parseConnections(node, art, List()) - } - } - - private def parseConnections[T <: Artifact](node: XdmNode, art: T, forbidden: List[QName]): T = { - art.parse(node) - - var implicitInline = false - for (child <- children(node)) { - if (forbidden.nonEmpty && forbidden.contains(child.getNodeName)) { - throw XProcException.xsElementNotAllowed(child.getNodeName, Some(new XProcLocation(child))) - } - - child.getNodeKind match { - case XdmNodeKind.ELEMENT => - child.getNodeName match { - case XProcConstants.p_empty => - art.addChild(parseEmpty(child)) - case XProcConstants.p_document => - art.addChild(parseDocument(child)) - case XProcConstants.p_inline => - art.addChild(parseInline(child)) - case XProcConstants.p_pipe => - art.addChild(parsePipe(child)) - case XProcConstants.p_documentation => - art.addChild(parseDocumentation(child)) - case XProcConstants.p_pipeinfo => - art.addChild(parsePipeInfo(child)) - case _ => - implicitInline = true - art.addChild(parseSyntheticInline(child)) - } - case XdmNodeKind.COMMENT => - if (implicitInline) { - throw XProcException.xsInlineCommentNotAllowed(child.getStringValue.trim(), Some(new XProcLocation(child))) - } - case XdmNodeKind.PROCESSING_INSTRUCTION => - if (implicitInline) { - throw XProcException.xsInlinePiNotAllowed(child.getStringValue.trim(), Some(new XProcLocation(child))) - } - case XdmNodeKind.TEXT => - if (implicitInline) { - throw XProcException.xsInlineTextNotAllowed(child.getStringValue.trim(), Some(new XProcLocation(child))) - } - throw XProcException.xsTextNotAllowed(child.getStringValue.trim(), Some(new XProcLocation(child))) - case _ => - throw new RuntimeException(s"Unexpected element kind: ${child.getNodeKind}") - } - } - - art - } - - private def parseInput(node: XdmNode): DeclareInput = { - parseConnections(node, new DeclareInput(config)) - } - - private def parseOutput(node: XdmNode): DeclareOutput = { - parseConnections(node, new DeclareOutput(config)) - } - - private def parseOption(node: XdmNode): DeclareOption = { - parseNoChildrenAllowed(node, new DeclareOption(config)) - } - - private def parseVariable(node: XdmNode): Variable = { - parseConnections(node, new Variable(config)) - } - - private def parseAtomicStep(node: XdmNode): AtomicStep = { - val atomic = new AtomicStep(config) - atomic.parse(node) - - for (child <- children(node)) { - child.getNodeKind match { - case XdmNodeKind.ELEMENT => - child.getNodeName match { - case XProcConstants.p_with_input => - atomic.addChild(parseWithInput(child)) - case XProcConstants.p_with_option => - atomic.addChild(parseWithOption(child)) - case XProcConstants.p_documentation => - atomic.addChild(parseDocumentation(child)) - case XProcConstants.p_pipeinfo => - atomic.addChild(parsePipeInfo(child)) - case _ => - throw new RuntimeException(s"Unexpected element: ${child.getNodeName}") - } - case XdmNodeKind.COMMENT => () - case XdmNodeKind.PROCESSING_INSTRUCTION => () - case XdmNodeKind.TEXT => - throw XProcException.xsTextNotAllowed(child.getStringValue.trim(), Some(new XProcLocation(child))) - case _ => - throw new RuntimeException(s"Unexpected element kind: ${child.getNodeKind}") - } - } - - atomic - } - - private def parseWithInput(node: XdmNode): WithInput = { - parseConnections(node, new WithInput(config)) - } - - private def parseWithOption(node: XdmNode): WithOption = { - parseConnections(node, new WithOption(config)) - } - - private def parseNoChildrenAllowed[T <: Artifact](node: XdmNode, empty: T): T = { - empty.parse(node) - - for (child <- children(node)) { - child.getNodeKind match { - case XdmNodeKind.ELEMENT => - child.getNodeName match { - case XProcConstants.p_documentation => - empty.addChild(parseDocumentation(child)) - case XProcConstants.p_pipeinfo => - empty.addChild(parsePipeInfo(child)) - case _ => - throw new RuntimeException("no elements allowed") - } - case XdmNodeKind.COMMENT => () - case XdmNodeKind.PROCESSING_INSTRUCTION => () - case XdmNodeKind.TEXT => - throw XProcException.xsTextNotAllowed(child.getStringValue.trim(), Some(new XProcLocation(child))) - case _ => - throw new RuntimeException(s"Unexpected element kind: ${child.getNodeKind}") - } - } - - empty - } - - private def parseEmpty(node: XdmNode): Empty = { - parseNoChildrenAllowed(node, new Empty(config)) - } - - private def parseDocument(node: XdmNode): Document = { - parseNoChildrenAllowed(node, new Document(config)) - } - - private def parsePipe(node: XdmNode): Pipe = { - parseNoChildrenAllowed(node, new Pipe(config)) - } - - private def parseFunction(node: XdmNode): DeclareFunction = { - parseNoChildrenAllowed(node, new DeclareFunction(config)) - } - - private def parseInline(node: XdmNode): Inline = { - val builder = new SaxonTreeBuilder(config) - builder.startDocument(node.getBaseURI) - val iter = node.axisIterator(Axis.CHILD) - while (iter.hasNext) { - val child = iter.next() - builder.addSubtree(child) - } - builder.endDocument() - - val excludeURIs = S9Api.excludeInlineURIs(node) - val inlineNode = S9Api.removeNamespaces(config, builder.result, excludeURIs, true) - - val inline = new Inline(config, inlineNode) - inline.parse(node) - inline - } - - private def parseSyntheticInline(node: XdmNode): Inline = { - val builder = new SaxonTreeBuilder(config) - builder.startDocument(node.getBaseURI) - builder.addSubtree(node) - builder.endDocument() - - val excludeURIs = S9Api.excludeInlineURIs(node) - val inlineNode = S9Api.removeNamespaces(config, builder.result, excludeURIs, true) - - if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { - throw new RuntimeException("Elements in the XProc namespace cannot be synthetic inlines") - } - - val inline = new Inline(config, inlineNode, true) - inline.parse(node) - inline - } - - private def parseDocumentation(node: XdmNode): Documentation = { - val builder = new SaxonTreeBuilder(config) - builder.startDocument(node.getBaseURI) - val iter = node.axisIterator(Axis.CHILD) - while (iter.hasNext) { - val child = iter.next() - builder.addSubtree(child) - } - builder.endDocument() - - val docs = new Documentation(config, builder.result) - docs.parse(node) - docs - } - - private def parsePipeInfo(node: XdmNode): PipeInfo = { - val builder = new SaxonTreeBuilder(config) - builder.startDocument(node.getBaseURI) - val iter = node.axisIterator(Axis.CHILD) - while (iter.hasNext) { - val child = iter.next() - builder.addSubtree(child) - } - builder.endDocument() - - val info = new PipeInfo(config, builder.result) - info.parse(node) - info - } - - def init_builtins(): List[Library] = { - val libs = ListBuffer.empty[Library] - val xpls = getClass.getClassLoader.getResources("com.xmlcalabash.library.xpl") - while (xpls.hasMoreElements) { - val xpl = xpls.nextElement() - val xmlbuilder = config.processor.newDocumentBuilder() - val stream = xpl.openStream() - val source = new SAXSource(new InputSource(stream)) - xmlbuilder.setDTDValidation(false) - xmlbuilder.setLineNumbering(true) - val libnode = xmlbuilder.build(source) - val library = loadLibrary(libnode) - library.loadImports() - library.updateInScopeSteps() - libs += library - } - libs.toList - } - - // ============================================================================ - - private def children(node: XdmNode): List[XdmNode] = { - children(node, true) - } - - private def children(node: XdmNode, ignoreWS: Boolean): List[XdmNode] = { - val list = ListBuffer.empty[XdmNode] - val iter = node.axisIterator(Axis.CHILD) - while (iter.hasNext) { - val child = iter.next() - child.getNodeKind match { - case XdmNodeKind.TEXT => - if (!ignoreWS || child.getStringValue.trim() != "") { - list += child - } - case _ => list += child - } - } - list.toList - } - - private class ProcessUseWhen(val staticContext: StaticContext) extends ProcessMatchingNodes { - private var useStack = ListBuffer.empty[Boolean] - private var inlineStack = ListBuffer.empty[Boolean] - - override def startDocument(node: XdmNode): Boolean = { - throw XProcException.xiThisCantHappen("Processing use-when matched a document node", None) - } - - override def endDocument(node: XdmNode): Unit = { - matcher.endDocument() - } - - override def startElement(node: XdmNode, attributes: AttributeMap): Boolean = { - val useWhenName = if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { - new FingerprintedQName("", "", "use-when") - } else { - new FingerprintedQName("p", XProcConstants.ns_p, "use-when") - } - - val useWhen = Option(attributes.get(useWhenName)) - - var use = true - val inline = inlineStack.nonEmpty && inlineStack.last - if (useWhen.isDefined && !inline) { - val exprContext = new StaticContext(config, None, node) - val expr = new XProcXPathExpression(exprContext, useWhen.get.getValue) - use = config.expressionEvaluator.booleanValue(expr, List(), Map(), None) - } - - if (use) { - matcher.location = node.getUnderlyingNode.saveLocation() - if (inline) { - matcher.addStartElement(node, attributes) - } else { - matcher.addStartElement(node, attributes.remove(useWhenName)) - } - } - useStack += use - inlineStack += ((inlineStack.nonEmpty && inlineStack.last) || node.getNodeName == XProcConstants.p_inline) - use - } - - override def attributes(node: XdmNode, matchingAttributes: AttributeMap, nonMatchingAttributes: AttributeMap): Option[AttributeMap] = { - throw new RuntimeException("This can't happen") - } - - override def endElement(node: XdmNode): Unit = { - if (useStack.last) { - matcher.addEndElement() - } - useStack = useStack.dropRight(1) - inlineStack = inlineStack.dropRight(1) - } - - override def text(node: XdmNode): Unit = { - throw XProcException.xiThisCantHappen("Processing use-when matched a text node", None) - } - - override def comment(node: XdmNode): Unit = { - throw XProcException.xiThisCantHappen("Processing use-when matched a comment node", None) - } - - override def pi(node: XdmNode): Unit = { - throw XProcException.xiThisCantHappen("Processing use-when matched a processing-instruction node", None) - } - } - -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Pipe.scala b/src/main/scala/com/xmlcalabash/model/xml/Pipe.scala deleted file mode 100644 index 36973ba..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Pipe.scala +++ /dev/null @@ -1,204 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.XdmNode - -class Pipe(override val config: XMLCalabash, shortcut: Option[String]) extends DataSource(config) { - def this(config: XMLCalabash) = { - this(config, None) - } - def this(config: XMLCalabash, shortcut: String) = { - this(config, Some(shortcut)) - } - - def this(pipe: Pipe) = { - this(pipe.config) - _step = pipe._step - _port = pipe._port - _link = pipe._link - } - - private var _step = Option.empty[String] - private var _port = Option.empty[String] - private var _link = Option.empty[Port] - - def step: String = _step.get - protected[model] def step_=(step: String): Unit = { - _step = Some(step) - } - def port: String = _port.get - - protected[model] def port_=(port: String): Unit = { - _port = Some(port) - } - def link: Option[Port] = _link - def link_=(port: Port): Unit = { - _link = Some(port) - } - - override protected[model] def parse(node: XdmNode): Unit = { - super.parse(node) - - _step = attr(XProcConstants._step) - _port = attr(XProcConstants._port) - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - // nop - } - - override protected[model] def makeBindingsExplicit(): Unit = { - for (child <- allChildren) { - child.makeBindingsExplicit() - } - - val env = environment() - val drp = env.defaultReadablePort - - if (_link.isEmpty) { - if (_step.isEmpty) { - if (drp.isEmpty) { - throw XProcException.xsPipeWithoutStepOrDrp(location) - } - _step = Some(drp.get.step.stepName) - } - - val step = env.step(_step.get) - - // Figure out what our containing step is. But along the way, note if - // we're inside a variable because they're allowed to read from the - // containing steps inputs. - var thisStep: Step = null - var varbinding = false - - var p: Option[Artifact] = Some(this) - while (p.isDefined) { - p.get match { - case step: Step => - thisStep = step - p = None - case _: Variable => - varbinding = true - p = p.get.parent - case _ => - p = p.get.parent - } - } - - if (_port.isEmpty) { - if (step.isEmpty) { - throw XProcException.xsPortNotReadableNoStep(_step.get, location) - } - if (thisStep.ancestor(step.get)) { - if (step.get.primaryInput.isEmpty) { - throw XProcException.xsPortNotReadableNoPrimaryInput(_step.get, location) - } else { - _port = Some(step.get.primaryInput.get.port) - } - } else { - if (step.get.primaryOutput.isEmpty) { - throw XProcException.xsPortNotReadableNoPrimaryOutput(_step.get, location) - } else { - _port = Some(step.get.primaryOutput.get.port) - } - } - } - - if (step.isEmpty) { - throw XProcException.xsPortNotReadableNoStep(_step.get, location) - } - - if (thisStep == step.get && !varbinding) { - // Special case of a loop - throw XProcException.xsLoop(_step.get, _port.get, location) - } - - for (child <- step.get.allChildren) { - child match { - case input: DeclareInput => - if (input.port == _port.get) { - _link = Some(input) - } - case output: DeclareOutput => - if (output.port == _port.get) { - _link = Some(output) - } - case output: WithOutput => - if (output.port == _port.get) { - _link = Some(output) - } - case _ => () - } - } - - if (_link.isEmpty) { - throw XProcException.xsPortNotReadable(_step.get, _port.get, location) - } - } - } - - override protected[model] def validateStructure(): Unit = { - if (allChildren.nonEmpty) { - throw new RuntimeException(s"Invalid content in $this") - } - } - - override protected[model] def normalizeToPipes(): Unit = { - // nop - } - - override protected[model] def insertPipe(source: Port, pipe: Pipe): Unit = { - if (link.get == source) { - val newPipe = new Pipe(config) - newPipe.step = pipe.step - newPipe.port = pipe.port - newPipe.link = pipe.link.get - parent.get.addChild(newPipe, this) - } - } - - override protected[model] def replumb(oldSource: Port, newSource: Port): Unit = { - if (link.get == oldSource) { - link = newSource - step = newSource.parent.get.asInstanceOf[Step].stepName - port = newSource.port - } - } - - override def graphEdges(runtime: XMLCalabashRuntime, parNode: Node): Unit = { - val toNode = parNode - val toPort = parent.get.asInstanceOf[Port].port - val fromNode = link.get.parent.get._graphNode.get - val fromPort = link.get.port - runtime.graph.addOrderedEdge(fromNode, fromPort, toNode, toPort) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startPipe(tumble_id, step, port) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endPipe() - } - - override def toString: String = { - val sstep = _step.getOrElse("???") - val sport = _port.getOrElse("???") - - if (tumble_id.startsWith("!syn")) { - s"p:pipe from $sstep/$sport" - } else { - s"p:pipe from $sstep/$sport $tumble_id" - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/PipeInfo.scala b/src/main/scala/com/xmlcalabash/model/xml/PipeInfo.scala deleted file mode 100644 index 4189826..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/PipeInfo.scala +++ /dev/null @@ -1,31 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{Axis, QName, XdmNode, XdmNodeKind} - -class PipeInfo(override val config: XMLCalabash, val info: XdmNode) extends Port(config) { - override protected[model] def validateStructure(): Unit = { - // nop - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - var root = Option.empty[QName] - val iter = info.axisIterator(Axis.CHILD) - while (root.isEmpty && iter.hasNext) { - val item = iter.next() - if (item.getNodeKind == XdmNodeKind.ELEMENT) { - root = Some(item.getNodeName) - } - } - - xml.startPipeInfo(tumble_id, root) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endPipeInfo() - } - override def toString: String = { - s"p:pipeinfo $tumble_id" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Port.scala b/src/main/scala/com/xmlcalabash/model/xml/Port.scala deleted file mode 100644 index 3639063..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Port.scala +++ /dev/null @@ -1,157 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.params.SelectFilterParams -import com.xmlcalabash.util.MediaType - -import scala.collection.mutable.ListBuffer - -class Port(override val config: XMLCalabash) extends Artifact(config) { - protected var _port = "" - protected[xml] var _sequence = Option.empty[Boolean] - protected[xml] var _primary = Option.empty[Boolean] - protected[xml] var _select = Option.empty[String] - protected[xml] var _content_types = List.empty[MediaType] - - protected var _href = Option.empty[String] - protected var _pipe = Option.empty[String] - - def port: String = _port - protected[model] def port_=(port: String): Unit = { - _port = port - } - def sequence: Boolean = _sequence.getOrElse(false) - protected[model] def sequence_=(seq: Boolean): Unit = { - _sequence = Some(seq) - } - def primary: Boolean = _primary.getOrElse(false) - protected[model] def primary_=(primary: Boolean): Unit = { - _primary = Some(primary) - } - - def select: Option[String] = _select - - def contentTypes: List[MediaType] = _content_types - def contentTypes_=(types: List[MediaType]): Unit = { - _content_types = types - } - - def step: NamedArtifact = { - if (parent.isDefined) { - parent.get match { - case art: NamedArtifact => art - case _ => throw new RuntimeException("parent of port isn't a named artifact?") - } - } else { - throw new RuntimeException("port has no parent?") - } - } - - def bindings: List[DataSource] = { - val lb = ListBuffer.empty[DataSource] - for (child <- allChildren) { - child match { - case ds: DataSource => lb += ds - case _ => () - } - } - lb.toList - } - - protected[model] def examineBindings(): Unit = { - if (_href.isDefined && _pipe.isDefined) { - throw XProcException.xsPipeAndHref(location) - } - - if (_href.isDefined && allChildren.nonEmpty) { - throw XProcException.xsHrefAndOtherSources(location) - } - - if (_pipe.isDefined && allChildren.nonEmpty) { - throw XProcException.xsPipeAndOtherSources(location) - } - - if (_href.isDefined) { - val doc = new Document(config) - doc.staticContext = staticContext - doc.href = _href.get - addChild(doc) - } - - if (_pipe.isDefined) { - for (shortcut <- _pipe.get.split("\\s+")) { - var port = Option.empty[String] - var step = Option.empty[String] - if (shortcut.contains("@")) { - val re = "(.*)@(.*)".r - shortcut match { - case re(pname, sname) => - if (pname != "") { - port = Some(pname) - } - if (sname == "") { - throw XProcException.xsInvalidPipeToken(shortcut, location) - } - step = Some(sname) - } - } else { - if (shortcut.trim() != "") { - port = Some(shortcut) - } - } - - val pipe = new Pipe(config) - if (step.isDefined) { - pipe.step = step.get - } - if (port.isDefined) { - pipe.port = port.get - } - addChild(pipe) - } - } - } - - override protected[model] def validateStructure(): Unit = { - for (child <- allChildren) { - child.validateStructure() - } - - var empty = false - var nonEmpty = false - var pns = false - var implinline = false - for (child <- allChildren) { - child match { - case _: Document => - nonEmpty = true - pns = true - case _: Empty => - empty = true - pns = true - case source: Inline => - nonEmpty = true - if (source.synthetic) { - implinline = true - } else { - pns = true - } - case _: Pipe => - nonEmpty = true - pns = true - case _: NamePipe => () - case _ => throw new RuntimeException(s"Unexpected port binding: $child") - } - } - - if (empty && nonEmpty) { - throw XProcException.xsNoSiblingsOnEmpty(location) - } - - if (pns && implinline) { - throw XProcException.xsInvalidPipeline("Cannot combine implicit inlines with elements from the p: namespace", location) - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Step.scala b/src/main/scala/com/xmlcalabash/model/xml/Step.scala deleted file mode 100644 index 573831f..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Step.scala +++ /dev/null @@ -1,126 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.TypeUtils -import net.sf.saxon.s9api.XdmNode - -import scala.collection.mutable.ListBuffer - -class Step(override val config: XMLCalabash) extends Artifact(config) with NamedArtifact { - protected[xmlcalabash] var _name = Option.empty[String] - override def stepName: String = _name.getOrElse(tumble_id) - protected[model] def stepName_=(name: String): Unit = { - _name = Some(name) - } - - protected[xml] var _depends = ListBuffer.empty[String] - def depends: List[String] = _depends.toList - - override def parse(node: XdmNode): Unit = { - super.parse(node) - _name = attr(XProcConstants._name) - - var _depstr = Option.empty[String] - - if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { - _depstr = attr(XProcConstants._depends) - if (attr(XProcConstants.p_depends).isDefined) { - throw XProcException.xsXProcNamespaceError(XProcConstants.p_depends, location) - } - } else { - _depstr = attr(XProcConstants.p_depends) - if (attr(XProcConstants._depends).isDefined) { - throw XProcException.xsUndeclaredOption(node.getNodeName, XProcConstants._depends, location) - } - } - - if (_depstr.isDefined) { - if (_depstr.get.trim == "") { - throw XProcException.xsBadTypeValue(_depstr.get, "name+", location) - } - - val tutils = new TypeUtils(config) - for (name <- _depstr.get.trim().split("\\s+")) { - if (!tutils.valueMatchesType(name, XProcConstants.xs_NCName)) { - throw XProcException.xsBadTypeValue(name, "NCName", location) - } - _depends += name - } - } - } - - override protected[model] def validateStructure(): Unit = { - for (child <- allChildren) { - child.validateStructure() - } - } - - protected[model] def primaryInput: Option[Port] = { - for (child <- allChildren) { - child match { - case input: DeclareInput => - if (input.primary) { - return Some(input) - } - case winput: WithInput => - if (winput.primary) { - return Some(winput) - } - case _ => () - } - } - None - } - - protected[model] def primaryOutput: Option[Port] = { - for (child <- allChildren) { - child match { - case output: DeclareOutput => - if (output.primary) { - return Some(output) - } - case woutput: WithOutput => - if (woutput.primary) { - return Some(woutput) - } - case _ => () - } - } - None - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - if (depends.nonEmpty) { - val thisNode = _graphNode.get - val env = environment() - for (stepName <- depends) { - val step = env.step(stepName) - if (step.isEmpty) { - throw XProcException.xsNotAStep(stepName, location) - } else { - thisNode.dependsOn(step.get._graphNode.get) - } - } - } - } - - def graphChildEdges(runtime: XMLCalabashRuntime): Unit = { - for (child <- allChildren) { - child match { - case _: Step => - child.graphEdges(runtime, _graphNode.get) - case _: Variable => - child.graphEdges(runtime, _graphNode.get) - case _ => () - } - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Try.scala b/src/main/scala/com/xmlcalabash/model/xml/Try.scala deleted file mode 100644 index 88bc17e..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Try.scala +++ /dev/null @@ -1,202 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ChooseStart, ContainerStart, Node, TryCatchStart} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmNode} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -class Try(override val config: XMLCalabash) extends Container(config) with NamedArtifact { - override def parse(node: XdmNode): Unit = { - super.parse(node) - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - var hasSubpipeline = false - var hasCatch = false - var hasFinally = false - val subpipeline = ListBuffer.empty[Artifact] - val catches = ListBuffer.empty[Artifact] - - for (child <- allChildren) { - child match { - case output: DeclareOutput => - if (hasSubpipeline || hasCatch || hasFinally) { - throw XProcException.xsInvalidPipeline("p:output is not allowed here", location) - } - subpipeline += output - case cat: Catch => - if (hasFinally) { - throw XProcException.xsInvalidPipeline("p:catch cannot follow p:finally", location) - } - catches += cat - hasCatch = true - case fin: Finally => - if (hasFinally) { - throw XProcException.xsInvalidTryCatch("at most one p:finally is allowed", location) - } - catches += fin - hasFinally = true - case step: Step => - if (hasCatch || hasFinally) { - throw XProcException.xsInvalidPipeline("steps cannot follow p:catch or p:finally", location) - } - subpipeline += step - hasSubpipeline = true - } - } - - if (!hasSubpipeline || !(hasCatch || hasFinally)) { - throw XProcException.xsInvalidTryCatch("p:try must have a subpipeline and at least one of p:catch and p:finally", location) - } - - val codes = mutable.HashSet.empty[QName] - var noCode = false - for (child <- children[Catch]) { - if (child.codes.isEmpty) { - if (noCode) { - throw XProcException.xsCatchMissingCode(location) - } - noCode = true - } else { - if (child.codes.nonEmpty && noCode) { - throw XProcException.xsCatchMissingCode(location) - } - } - for (code <- child.codes) { - if (codes.contains(code)) { - throw XProcException.xsCatchBadCode(code, location) - } - codes += code - } - } - - if (subpipeline.size > 1 || !subpipeline.head.isInstanceOf[Group]) { - val group = new Group(config) - for (child <- subpipeline) { - group.addChild(child) - } - removeChildren() - addChild(group) - for (child <- catches) { - addChild(child) - } - } - - for (child <- allChildren) { - child.makeStructureExplicit() - } - - val outputSet = mutable.HashSet.empty[String] - var primaryOutput = Option.empty[String] - - for (branch <- allChildren) { - branch match { - case group: Group => - for (child <- group.children[DeclareOutput]) { - outputSet += child.port - if (child.primary) { - if (primaryOutput.isDefined) { - if (primaryOutput.get != child.port) { - throw XProcException.xsBadTryOutputs(primaryOutput.get, child.port, location) - } - } else { - primaryOutput = Some(child.port) - } - } - } - case cat: Catch => - for (child <- cat.children[DeclareOutput]) { - outputSet += child.port - if (child.primary) { - if (primaryOutput.isDefined) { - if (primaryOutput.get != child.port) { - println(child.primary) - throw XProcException.xsBadTryOutputs(primaryOutput.get, child.port, location) - } - } else { - primaryOutput = Some(child.port) - } - } - } - case fin: Finally => - for (child <- fin.children[DeclareOutput]) { - if (outputSet.contains(child.port)) { - throw XProcException.xsInvalidFinallyPortName(child.port, location) - throw new RuntimeException("Bad output in p:finally") - } - outputSet += child.port - } - case _ => () - } - } - - val first = firstChild - for (port <- outputSet) { - val woutput = new WithOutput(config) - woutput.port = port - woutput.primary = (primaryOutput.isDefined && primaryOutput.get == port) - addChild(woutput, first) - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - for (child <- allChildren) { - child.makeBindingsExplicit() - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[ContainerStart] - val node = start.addTryCatch(stepName, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - for (child <- children[Container]) { - child.graphEdges(runtime, _graphNode.get) - } - - val tryNode = _graphNode.get.asInstanceOf[TryCatchStart] - for (child <- allChildren) { - child match { - case group: Group => - for (output <- group.children[DeclareOutput]) { - runtime.graph.addOrderedEdge(group._graphNode.get, output.port, tryNode, output.port) - } - case cat: Catch => - for (output <- cat.children[DeclareOutput]) { - runtime.graph.addOrderedEdge(cat._graphNode.get, output.port, tryNode, output.port) - } - case fin: Finally => - for (output <- fin.children[DeclareOutput]) { - runtime.graph.addOrderedEdge(fin._graphNode.get, output.port, tryNode, output.port) - } - case _ => () - } - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startTry(tumble_id, stepName) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endTry() - } - - override def toString: String = { - s"p:try $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/Variable.scala b/src/main/scala/com/xmlcalabash/model/xml/Variable.scala deleted file mode 100644 index 6065192..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Variable.scala +++ /dev/null @@ -1,42 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.{ExceptionCode, ModelException} -import com.xmlcalabash.runtime.params.XPathBindingParams -import com.xmlcalabash.runtime.{ExprParams, XMLCalabashRuntime, XProcXPathExpression} -import com.xmlcalabash.util.xc.ElaboratedPipeline - -class Variable(override val config: XMLCalabash) extends NameBinding(config) { - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - if (static) { - // Statics have already been evaluated, they don't appear in the graph - return - } - - val container = this.parent.get - val cnode = container._graphNode.get.asInstanceOf[ContainerStart] - - val params = new XPathBindingParams(collection) - val init = new XProcXPathExpression(staticContext, _select.getOrElse("()"), as, _allowedValues, params) - val node = cnode.addOption(_name.getClarkName, init, xpathBindingParams()) - _graphNode = Some(node) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - if (static) { - return - } - - xml.startVariable(tumble_id, tumble_id, name) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endWithVariable() - } - - override def toString: String = { - s"p:variable $name $tumble_id" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/Viewport.scala b/src/main/scala/com/xmlcalabash/model/xml/Viewport.scala deleted file mode 100644 index efc0bcc..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/Viewport.scala +++ /dev/null @@ -1,177 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node} -import com.jafpl.steps.Manifold -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.runtime.params.ContentTypeCheckerParams -import com.xmlcalabash.util.MediaType -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmNode} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -class Viewport(override val config: XMLCalabash) extends Container(config) with NamedArtifact { - private var _match: String = _ - protected var _dependentNameBindings: ListBuffer[NamePipe] = ListBuffer.empty[NamePipe] - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(XProcConstants._match)) { - _match = attr(XProcConstants._match).get - } else { - throw new RuntimeException("Viewport must have match") - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeStructureExplicit(): Unit = { - val first = firstChild - if (firstWithInput.isDefined) { - val fwi = firstWithInput.get - fwi.port match { - case "" => - // It may be anonymous in XProc, but it mustn't be anonymous in the graph - fwi.port = "source" - case "source" => () - case _ => throw XProcException.xiThisCantHappen(s"Viewport withinput is named '${fwi.port}''", location) - } - } else { - val input = new WithInput(config) - input.port = "source" - input.primary = true - addChild(input, first) - } - - val current = new DeclareInput(config) - current.port = "current" - current.primary = true - addChild(current, first) - - makeContainerStructureExplicit() - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - val env = environment() - - val bindings = mutable.HashSet.empty[QName] - bindings ++= staticContext.findVariableRefsInString(_match) - - for (ref <- bindings) { - val binding = env.variable(ref) - if (binding.isEmpty) { - throw XProcException.xsStaticErrorInExpression(s"$$${ref.toString}", "Reference to undefined variable", location) - } - if (!binding.get.static) { - val pipe = new NamePipe(config, ref, binding.get.tumble_id, binding.get) - _dependentNameBindings += pipe - addChild(pipe) - } - } - } - - override protected[model] def normalizeToPipes(): Unit = { - super.normalizeToPipes() - - // Viewport needs a content type checker in front of its source - val input = children[WithInput].head - - logger.debug(s"Adding content-type-checker for viewport source") - val params = new ContentTypeCheckerParams(input.port, List(MediaType.XML, MediaType.HTML, MediaType.XHTML), staticContext, None, - XProcException.err_xd0072, inputPort = true, true) - val atomic = new AtomicStep(config, params) - atomic.stepType = XProcConstants.cx_content_type_checker - // Put this outside the viewport so that its output is attached to the viewport input, not the viewport output - parent.get.addChild(atomic) - - val winput = new WithInput(config) - winput.port = "source" - atomic.addChild(winput) - - val woutput = new WithOutput(config) - woutput.port = "result" - atomic.addChild(woutput) - - val pipes = ListBuffer.empty[Pipe] ++ input.children[Pipe] - input.removeChildren() - for (origpipe <- pipes) { - winput.addChild(origpipe) - - val opipe = new Pipe(config) - opipe.step = atomic.stepName - opipe.port = "result" - opipe.link = atomic.children[WithOutput].head - input.addChild(opipe) - opipe.makeBindingsExplicit() - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - val start = parent.asInstanceOf[ContainerStart] - val context = staticContext.withStatics(inScopeStatics) - val composer = new XMLViewportComposer(config, context, _match) - val node = start.addViewport(composer, stepName, containerManifold) - _graphNode = Some(node) - super.graphNodes(runtime, parent) - - // The binding links we created earlier now need to be patched so that this - // is the node they go to. - for (np <- _dependentNameBindings) { - val binding = findInScopeOption(np.name) - if (binding.isDefined) { - np.patchNode(binding.get.graphNode.get) - } - } - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - super.graphEdges(runtime, parent) - - val winput = firstWithInput - if (winput.isDefined) { - for (child <- winput.get.allChildren) { - child match { - case pipe: Pipe => - pipe.graphEdges(runtime, _graphNode.get) - case pipe: NamePipe => - pipe.graphEdges(runtime, _graphNode.get) - case _ => () - } - } - } - - for (pipe <- children[NamePipe]) { - pipe.graphEdges(runtime, _graphNode.get) - } - - for (output <- children[DeclareOutput]) { - for (pipe <- output.children[Pipe]) { - runtime.graph.addOrderedEdge(pipe.link.get.parent.get._graphNode.get, pipe.port, _graphNode.get, output.port) - } - } - - graphChildEdges(runtime) - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startViewport(tumble_id, stepName, _match) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endViewport() - } - - override def toString: String = { - s"p:viewport $stepName" - } -} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xml/When.scala b/src/main/scala/com/xmlcalabash/model/xml/When.scala deleted file mode 100644 index b20f77b..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/When.scala +++ /dev/null @@ -1,72 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmNode} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -class When(override val config: XMLCalabash) extends ChooseBranch(config) { - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - val coll = attr(XProcConstants._collection) - if (coll.isDefined) { - if (List("1", "true", "yes").contains(coll.get)) { - _collection = true - } else { - if (List("0", "false", "no").contains(coll.get)) { - _collection = false - } else { - throw XProcException.xsBadTypeValue(coll.get, "xs:boolean", location) - } - } - } - - if (attributes.contains(XProcConstants._test)) { - test = attr(XProcConstants._test).get - } else { - throw XProcException.xsMissingRequiredAttribute(XProcConstants._test, location) - } - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - val bindings = mutable.HashSet.empty[QName] - - val env = environment() - - for (ref <- bindings) { - val binding = env.variable(ref) - if (binding.isEmpty) { - throw new RuntimeException("Reference to undefined variable") - } - if (!binding.get.static) { - val pipe = new NamePipe(config, ref, binding.get.tumble_id, binding.get) - addChild(pipe) - } - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startWhen(tumble_id, stepName, _test) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endWhen() - } - - override def toString: String = { - s"p:when $stepName" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/WithInput.scala b/src/main/scala/com/xmlcalabash/model/xml/WithInput.scala deleted file mode 100644 index 81b6709..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/WithInput.scala +++ /dev/null @@ -1,226 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.{XMLCalabash, exceptions} -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} -import com.xmlcalabash.runtime.params.SelectFilterParams -import com.xmlcalabash.runtime.{StaticContext, XMLCalabashRuntime, XmlPortSpecification} -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.functions.ConstantFunction.{False, True} -import net.sf.saxon.s9api.{QName, XdmNode} - -class WithInput(override val config: XMLCalabash) extends Port(config) { - private val _exclude_inline_prefixes = List.empty[String] - private var _context: StaticContext = _ - private var portSpecified = false - - def exclude_inline_prefixes: List[String] = _exclude_inline_prefixes - - override def parse(node: XdmNode): Unit = { - super.parse(node) - - if (attributes.contains(XProcConstants._port)) { - portSpecified = true - _port = staticContext.parseNCName(attr(XProcConstants._port)).get - } - _context = new StaticContext(config, Some(this), node) - _select = attr(XProcConstants._select) - - _href = attr(XProcConstants._href) - _pipe = attr(XProcConstants._pipe) - - if (attributes.nonEmpty) { - val badattr = attributes.keySet.head - throw XProcException.xsBadAttribute(badattr, location) - } - } - - override protected[model] def makeBindingsExplicit(): Unit = { - if (portSpecified) { - parent.get match { - case _: ForEach => - throw XProcException.xsPortNotAllowed(_port, "p:for-each", location) - case _: Viewport => - throw XProcException.xsPortNotAllowed(_port, "p:viewport", location) - case _: Choose => - throw XProcException.xsPortNotAllowed(_port, "p:choose or p:if", location) - case _: When => - throw XProcException.xsPortNotAllowed(_port, "p:when", location) - case _: ForLoop => - throw XProcException.xsPortNotAllowed(_port, "cx:loop", location) - case _: ForUntil => - throw XProcException.xsPortNotAllowed(_port, "cx:until", location) - case _: ForWhile => - throw XProcException.xsPortNotAllowed(_port, "cx:while", location) - case _ => () - } - } - - examineBindings() - super.makeBindingsExplicit() - - var primaryInput = primary - parent.get match { - case atom: AtomicStep => - val decl = declaration(atom.stepType).get - primaryInput = decl.input(_port, None).primary - sequence = decl.input(_port, None).sequence - case _ => () - } - - if (allChildren.nonEmpty) { - // If there are explicit children, we'll check them elsewhere - return - } - - val env = environment() - val drp = env.defaultReadablePort - - if (primaryInput && drp.isDefined) { - val pipe = new Pipe(config) - pipe.port = drp.get.port - pipe.step = drp.get.step.stepName - pipe.link = drp.get - addChild(pipe) - return - } - - // This is err:XS0032 unless it's a special case - - // One of the special cases is, if this is an atomic step, and that - // step has a default input for this port, then this is not an error, - // the default input should be used. UGH. - parent.get match { - case atomic: AtomicStep => - if (declaration(atomic.stepType).isDefined) { - val sig = declaration(atomic.stepType).get - val psig = sig.input(_port, location) - if (psig.defaultBindings.nonEmpty) { - for (binding <- psig.defaultBindings) { - binding match { - case empty: Empty => - addChild(new Empty(empty)) - case inline: Inline => - val x= new Inline(inline) - addChild(x) - case document: Document => - addChild(new Document(document)) - case _ => - throw XProcException.xiThisCantHappen("Default input is not empty, inline, or document", location) - } - } - return - } - } - case _ => () - } - - var raiseError = true - if (synthetic) { - // All the other special cases involve synthetic elements - parent.get match { - case _: Choose => raiseError = false - case _: When => raiseError = false - case _: Otherwise => raiseError = false - case _ => - if (parent.get.parent.isDefined - && parent.get.parent.get.synthetic - && parent.get.parent.get.isInstanceOf[Otherwise]) { - raiseError = false - } - } - } - - if (raiseError) { - if (primary) { - throw XProcException.xsUnconnectedPrimaryInputPort(step.stepName, port, location) - } else { - throw XProcException.xsUnconnectedInputPort(step.stepName, port, location) - } - } - } - - override protected[model] def addFilters(): Unit = { - super.addFilters() - if (select.isEmpty) { - return - } - - val context = staticContext.withStatics(inScopeStatics) - - val ispec = if (sequence) { - XmlPortSpecification.ANYSOURCESEQ - } else { - XmlPortSpecification.ANYSOURCE - } - - val params = new SelectFilterParams(context, select.get, port, ispec) - val filter = new AtomicStep(config, params) - filter.stepType = XProcConstants.cx_select_filter - - val finput = new WithInput(config) - finput.port = "source" - finput.primary = true - - val foutput = new WithOutput(config) - foutput.port = "result" - foutput.primary = true - - filter.addChild(finput) - filter.addChild(foutput) - - for (name <- staticContext.findVariableRefsInString(_select.get)) { - var binding = _inScopeDynamics.get(name) - if (binding.isDefined) { - val npipe = new NamePipe(config, name, binding.get.tumble_id, binding.get) - filter.addChild(npipe) - } else { - binding = _inScopeStatics.get(name.getClarkName) - if (binding.isEmpty) { - throw new RuntimeException(s"Reference to variable not in scope: $name") - } - } - } - - val step = parent.get - val container = step.parent.get.asInstanceOf[Container] - container.addChild(filter, step) - - for (child <- allChildren) { - child match { - case pipe: Pipe => - finput.addChild(pipe) - } - } - removeChildren() - - val pipe = new Pipe(config) - pipe.step = filter.stepName - pipe.port = "result" - pipe.link = foutput - addChild(pipe) - } - - override def graphEdges(runtime: XMLCalabashRuntime, parNode: Node): Unit = { - for (child <- allChildren) { - child.graphEdges(runtime, parNode) - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startWithInput(tumble_id, tumble_id, port) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endWithInput() - } - - override def toString: String = { - if (tumble_id.startsWith("!syn")) { - s"p:with-input $port" - } else { - s"p:with-input $port $tumble_id" - } - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/WithOption.scala b/src/main/scala/com/xmlcalabash/model/xml/WithOption.scala deleted file mode 100644 index d1a1d57..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/WithOption.scala +++ /dev/null @@ -1,206 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.{ContainerStart, Node} -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.config.OptionSignature -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.messages.{XdmNodeItemMessage, XdmValueItemMessage} -import com.xmlcalabash.model.util.ValueParser -import com.xmlcalabash.runtime._ -import com.xmlcalabash.runtime.params.XPathBindingParams -import com.xmlcalabash.util.xc.ElaboratedPipeline -import com.xmlcalabash.util.{S9Api, TypeUtils} -import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmMap, XdmValue} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -class WithOption(override val config: XMLCalabash) extends NameBinding(config) { - val typeUtils = new TypeUtils(config) - private val _precOptions = ListBuffer.empty[OptionSignature] - - def this(config: XMLCalabash, name: QName) = { - this(config) - _name = name - } - - def precedingOption(opt: OptionSignature): Unit = { - _precOptions += opt - } - - override protected[model] def makeBindingsExplicit(): Unit = { - super.makeBindingsExplicit() - - val env = environment() - - val bindings = mutable.HashSet.empty[QName] - if (_avt.isDefined) { - val avt = staticContext.parseAvt(_avt.get) - bindings ++= staticContext.findVariableRefsInAvt(avt) - if (bindings.isEmpty && parent.get.isInstanceOf[AtomicStep]) { - val depends = staticContext.dependsOnContextAvt(avt) - if (!depends) { - // When you come back to optimize this, make sure ab-option-056 passes. - /* - val expr = new XProcVtExpression(staticContext, _avt.get, true) - var msg = try { - config.expressionEvaluator.newInstance().value(expr, List(), inScopeStatics, None) - } catch { - case ex: Throwable => - throw XProcException.xdGeneralError(ex.getMessage, location) - } - // Ok, now we have a string value - val avalue = msg.item.getUnderlyingValue.getStringValue - var tvalue = typeUtils.castAtomicAs(XdmAtomicValue.makeAtomicValue(avalue), Some(declaredType), staticContext) - if (as.isDefined) { - tvalue = typeUtils.castAtomicAs(tvalue, as, staticContext) - } - msg = new XdmValueItemMessage(tvalue, XProcMetadata.XML, staticContext) - staticValue = msg - */ - } - } - } else if (_select.isDefined) { - bindings ++= staticContext.findVariableRefsInString(_select.get) - if (bindings.isEmpty && parent.get.isInstanceOf[AtomicStep]) { - //val depends = collection || staticContext.dependsOnContextString(_select.get) - val depends = true - if (!depends) { - val checkas = if (qnameKeys) { - None - } else { - as - } - val opts = new XPathBindingParams(Map.empty[QName, XdmValue], collection) - val expr = new XProcXPathExpression(staticContext, _select.get, checkas, allowedValues, opts) - val msg = config.expressionEvaluator.newInstance().value(expr, List(), inScopeStatics, Some(opts)) - - if (qnameKeys) { - msg.item match { - case map: XdmMap => - staticValue = new XdmValueItemMessage(S9Api.forceQNameKeys(map.getUnderlyingValue, staticContext), XProcMetadata.XML, staticContext) - case _ => throw new RuntimeException("qname map type didn't evaluate to a map") - } - } else { - staticValue = new XdmValueItemMessage(msg.item, XProcMetadata.XML, staticContext) - } - } - } - } else { - throw new RuntimeException("With option has neither AVT nor select?") - } - - var nonStaticBindings = false - for (ref <- bindings) { - val binding = env.variable(ref) - if (binding.isEmpty) { - throw new RuntimeException("Reference to undefined variable") - } - nonStaticBindings = nonStaticBindings || !binding.get.static - } - - if (nonStaticBindings) { - var winput = firstWithInput - if (winput.isEmpty) { - val input = new WithInput(config) - input.port = "source" - addChild(input) - winput = Some(input) - } - } - - // FIXME: does this result in duplicates sometimes? - for (ref <- bindings) { - val binding = env.variable(ref).get - if (!binding.static) { - val pipe = new NamePipe(config, ref, binding.tumble_id, binding) - _dependentNameBindings += pipe - addChild(pipe) - } - } - } - - override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { - if (staticValue.isDefined) { - return - } - - val container = this.parent.get.parent.get - val cnode = container._graphNode.get.asInstanceOf[ContainerStart] - val statics = mutable.HashMap.empty[QName, XdmValue] - for ((name,smsg) <- inScopeStatics) { - val qname = ValueParser.parseClarkName(name) - smsg match { - case msg: XdmNodeItemMessage => - statics.put(qname, msg.item) - case msg: XdmValueItemMessage => - statics.put(qname, msg.item) - } - } - - val params = new XPathBindingParams(statics.toMap, collection) - val init = if (_avt.isDefined) { - val expr = staticContext.parseAvt(_avt.get) - new XProcVtExpression(staticContext, expr, true) - } else { - val params = new XPathBindingParams(statics.toMap, collection) - new XProcXPathExpression(staticContext, _select.getOrElse("()"), as, _allowedValues, params) - } - val node = cnode.addOption(_name.getClarkName, init, params) - _graphNode = Some(node) - - // The binding links we created earlier now need to be patched so that this - // is the node they go to. - for (np <- _dependentNameBindings) { - val binding = findInScopeOption(np.name) - if (binding.isDefined) { - np.patchNode(binding.get.graphNode.get) - } - } - } - - override def graphEdges(runtime: XMLCalabashRuntime, parNode: Node): Unit = { - if (staticValue.isDefined) { - return - } - - val env = environment() - for (stepName <- depends) { - val step = env.step(stepName) - if (step.isEmpty) { - throw XProcException.xsNotAStep(stepName, location) - } else { - _graphNode.get.dependsOn(step.get._graphNode.get) - } - } - - val toNode = parNode - val fromPort = "result" - val fromNode = _graphNode.get - val toPort = "#bindings" - runtime.graph.addEdge(fromNode, fromPort, toNode, toPort) - - for (child <- allChildren) { - child.graphEdges(runtime, _graphNode.get) - } - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - if (staticValue.isEmpty) { - xml.startWithOption(tumble_id, tumble_id, name) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endWithOption() - } - } - - override def toString: String = { - if (tumble_id.startsWith("!syn")) { - s"p:with-option $name" - } else { - s"p:with-option $name $tumble_id" - } - } - -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/WithOutput.scala b/src/main/scala/com/xmlcalabash/model/xml/WithOutput.scala deleted file mode 100644 index 369fad4..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/WithOutput.scala +++ /dev/null @@ -1,29 +0,0 @@ -package com.xmlcalabash.model.xml - -import com.jafpl.graph.Node -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.runtime.XMLCalabashRuntime -import com.xmlcalabash.util.xc.ElaboratedPipeline -import net.sf.saxon.s9api.XdmNode - -class WithOutput(override val config: XMLCalabash) extends Port(config) { - override def parse(node: XdmNode): Unit = { - throw new RuntimeException("This is a purely synthetic element") - } - - override def graphEdges(runtime: XMLCalabashRuntime, parent: Node): Unit = { - // nop - } - - override def xdump(xml: ElaboratedPipeline): Unit = { - xml.startWithOutput(tumble_id, tumble_id, port, sequence) - for (child <- rawChildren) { - child.xdump(xml) - } - xml.endWithOutput() - } - - override def toString: String = { - s"p:with-output $port${if (sequence) "*" else ""}" - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xml/XMLContext.scala b/src/main/scala/com/xmlcalabash/model/xml/XMLContext.scala deleted file mode 100644 index 16bb73d..0000000 --- a/src/main/scala/com/xmlcalabash/model/xml/XMLContext.scala +++ /dev/null @@ -1,254 +0,0 @@ -package com.xmlcalabash.model.xml - -import java.net.URI -import com.jafpl.graph.Location -import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.{ValueParser, XProcConstants} -import com.xmlcalabash.runtime.StaticContext -import com.xmlcalabash.util.{MediaType, TypeUtils, ValueTemplateParser} -import net.sf.saxon.expr.parser.ExpressionTool -import net.sf.saxon.s9api.{ItemType, QName, SaxonApiException, SequenceType, XdmAtomicValue} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -object XMLContext { - private val contextFunctions = - List(XProcConstants.p_iteration_size, XProcConstants.p_iteration_position, - XProcConstants.fn_collection, XProcConstants._collection) -} - -class XMLContext(override val config: XMLCalabash, override val artifact: Option[Artifact]) extends StaticContext(config, artifact) { - def this(config: XMLCalabash, artifact: Artifact) = { - this(config, Some(artifact)) - } - - def this(config: XMLCalabash) = { - this(config, None) - } - - def this(config: XMLCalabash, artifact: Artifact, baseURI: Option[URI], ns: Map[String,String], location: Option[Location]) = { - this(config, Some(artifact)) - _baseURI = baseURI - _inScopeNS = ns - _location = location - } - - def this(config: XMLCalabash, baseURI: Option[URI], ns: Map[String,String], location: Option[Location]) = { - this(config, None) - _baseURI = baseURI - _inScopeNS = ns - _location = location - } - - val typeUtils = new TypeUtils(config, this) - - def parseBoolean(value: Option[String]): Option[Boolean] = { - if (value.isDefined) { - if (value.get == "true" || value.get == "false") { - Some(value.get == "true") - } else { - throw XProcException.xsBadTypeValue(value.get, "boolean", location) - } - } else { - None - } - } - - def parseQName(name: String): QName = { - parseQName(Some(name)).get - } - - def parseQName(name: Option[String]): Option[QName] = { - if (name.isDefined) { - val eqname = "^Q\\{(.*)\\}(\\S+)$".r - val qname = name.get match { - case eqname(uri,local) => Some(new QName(uri, local)) - case _ => - if (name.get.contains(":")) { - val pos = name.get.indexOf(':') - val prefix = name.get.substring(0, pos) - val local = name.get.substring(pos+1) - if (nsBindings.contains(prefix)) { - Some(new QName(prefix, nsBindings(prefix), local)) - } else { - throw XProcException.xdCannotResolveQName(name.get, location) - } - } else { - Some(new QName("", name.get)) - } - } - - val prefix = qname.get.getPrefix - val local = qname.get.getLocalName - if (prefix != null && !"".equals(prefix)) { - if (parseNCName(Some(prefix)).isDefined && parseNCName(Some(local)).isDefined) { - return qname - } - } else { - if (parseNCName(Some(local)).isDefined) { - return qname - } - } - // This will have already happened in the calls to parseNCName above, but - // putting it here satisfies the compiler. - throw XProcException.xsBadTypeValue(name.get, "NCName", location) - } else { - None - } - } - - def parseNCName(name: Option[String]): Option[String] = { - if (name.isDefined) { - try { - val ncname = TypeUtils.castAtomicAs(new XdmAtomicValue(name.get), ItemType.NCNAME, null) - Some(ncname.getStringValue) - } catch { - case _: SaxonApiException => - throw XProcException.xsBadTypeValue(name.get, "NCName", location) - case e: Exception => - throw e - } - } else { - None - } - } - - def parseContentTypes(ctypes: Option[String]): List[MediaType] = { - if (ctypes.isDefined) { - try { - MediaType.parseList(ctypes.get).toList - } catch { - case ex: XProcException => - if (ex.code == XProcException.err_xc0070) { - // Map to the static error... - throw XProcException.xsUnrecognizedContentTypeShortcut(ex.details.head.toString, ex.location) - } else { - throw ex - } - } - } else { - List.empty[MediaType] - } - } - - def parseSequenceType(seqType: Option[String]): Option[SequenceType] = { - typeUtils.parseSequenceType(seqType) - } - - def parseAvt(value: String): List[String] = { - val parser = new ValueTemplateParser(value) - parser.template() - } - - def findVariableRefsInAvt(list: List[String]): Set[QName] = { - val variableRefs = mutable.HashSet.empty[QName] - - var avt = false - for (substr <- list) { - if (avt) { - variableRefs ++= findVariableRefsInString(substr) - } - avt = !avt - } - - variableRefs.toSet - } - - def findVariableRefsInString(text: String): Set[QName] = { - findVariableRefsInString(Some(text)) - } - - def findVariableRefsInString(text: Option[String]): Set[QName] = { - val variableRefs = mutable.HashSet.empty[QName] - - if (text.isDefined) { - val parser = config.expressionParser - parser.parse(text.get) - for (ref <- parser.variableRefs) { - val qname = ValueParser.parseQName(ref, this) - variableRefs += qname - } - } - - variableRefs.toSet - } - - def findFunctionRefsInAvt(list: List[String]): Set[QName] = { - val functionRefs = mutable.HashSet.empty[QName] - - var avt = false - for (substr <- list) { - if (avt) { - functionRefs ++= findFunctionRefsInString(substr) - } - avt = !avt - } - - functionRefs.toSet - } - - def findFunctionRefsInString(text: String): Set[QName] = { - findFunctionRefsInString(Some(text)) - } - - def findFunctionRefsInString(text: Option[String]): Set[QName] = { - val functionRefs = mutable.HashSet.empty[QName] - - if (text.isDefined) { - val parser = config.expressionParser - parser.parse(text.get) - for (ref <- parser.functionRefs) { - val qname = ValueParser.parseQName(ref, this) - functionRefs += qname - } - } - - functionRefs.toSet - } - - def dependsOnContextAvt(list: List[String]): Boolean = { - var depends = false - - var avt = false - for (substr <- list) { - if (avt) { - depends = depends || dependsOnContextString(substr) - } - avt = !avt - } - - depends - } - - def dependsOnContextString(expr: String): Boolean = { - var depends = false - for (func <- findFunctionRefsInString(expr)) { - depends = depends || XMLContext.contextFunctions.contains(func) - } - val vars = findVariableRefsInString(expr) - - if (!depends) { - val xcomp = config.processor.newXPathCompiler() - for ((prefix, uri) <- nsBindings) { - xcomp.declareNamespace(prefix, uri) - } - for (name <- vars) { - xcomp.declareVariable(name) - } - val xexec = xcomp.compile(expr) - val xexpr = xexec.getUnderlyingExpression.getInternalExpression - ExpressionTool.dependsOnFocus(xexpr) - } else { - true - } - } - - object StateChange { - val STRING = 0 - val EXPR = 1 - val SQUOTE = 2 - val DQUOTE = 3 - } -} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/Loader.scala b/src/main/scala/com/xmlcalabash/model/xxml/Loader.scala new file mode 100644 index 0000000..9ac8ef1 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/Loader.scala @@ -0,0 +1,546 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.util.{TypeUtils, XdmLocation} +import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} +import net.sf.saxon.s9api.{Axis, XdmNode, XdmNodeKind} + +import scala.collection.mutable.ListBuffer +import scala.jdk.CollectionConverters.IteratorHasAsScala + +class Loader(parser: XParser, hier: NodeHierarchy) { + private val config = parser.config + private val _exceptions = ListBuffer.empty[Exception] + private var _declContainer: XDeclContainer = _ + private var _syntheticLibrary = Option.empty[XLibrary] + + if (!useWhen(hier.root)) { + throw XProcException.xiUserError("Use-when is false on the root element") + } + + if (hier.root.getNodeName == XProcConstants.p_declare_step) { + _declContainer = parseDeclareStep(hier.root) + } else { + _declContainer = parseLibrary(hier.root) + } + + def declaredStep: Option[XDeclareStep] = { + _declContainer match { + case step: XDeclareStep => Some(step) + case _ => None + } + } + + def library: Option[XLibrary] = { + _declContainer match { + case lib: XLibrary => + Some(lib) + case step: XDeclareStep => + if (_syntheticLibrary.isEmpty) { + val library = new XLibrary(config, hier.baseURI) + library.staticContext = new XArtifactContext(library, hier.root) + library.synthetic = true + library.syntheticName = XProcConstants.p_library + library.addChild(step) + _syntheticLibrary = Some(library) + } + _syntheticLibrary + case _ => + throw XProcException.xiThisCantHappen("Declared container is neither step nor library?") + } + } + + def exceptions: List[Exception] = _exceptions.toList + + private def useWhen(node: XdmNode): Boolean = hier.useWhen(node) + + private def parseChildren(node: XdmNode): List[XArtifact] = { + val children = ListBuffer.empty[XArtifact] + for (child <- node.axisIterator(Axis.CHILD).asScala) { + try { + child.getNodeKind match { + case XdmNodeKind.TEXT => + if (child.getStringValue.trim != "") { + throw XProcException.xsTextNotAllowed(child.getStringValue, XdmLocation.from(node)) + } + case XdmNodeKind.COMMENT => + () + case XdmNodeKind.PROCESSING_INSTRUCTION => + () + case XdmNodeKind.ELEMENT => + if (useWhen(child)) { + child.getNodeName match { + case XProcConstants.p_declare_step => + children += parseDeclareStep(child) + case XProcConstants.p_library => + children += parseLibrary(child) + case XProcConstants.p_catch => + children += parseCatch(child) + case XProcConstants.p_choose => + children += parseChoose(child) + case XProcConstants.p_document => + children += parseDocument(child) + case XProcConstants.p_documentation => + children += parseDocumentation(child) + case XProcConstants.p_empty => + children += parseEmpty(child) + case XProcConstants.p_finally => + children += parseFinally(child) + case XProcConstants.p_for_each => + children += parseForEach(child) + case XProcConstants.p_group => + children += parseGroup(child) + case XProcConstants.p_if => + children += parseIf(child) + case XProcConstants.p_import => + val imported = parseImport(child) + if (imported.isDefined) { + children += imported.get + } + case XProcConstants.p_import_functions => + children += parseImportFunctions(child) + case XProcConstants.p_inline => + children += parseInline(child) + case XProcConstants.p_input => + children += parseInput(child) + case XProcConstants.p_option => + children += parseOption(child) + case XProcConstants.p_otherwise => + children += parseOtherwise(child) + case XProcConstants.p_output => + children += parseOutput(child) + case XProcConstants.p_pipe => + children += parsePipe(child) + case XProcConstants.p_pipeinfo => + children += parsePipeinfo(child) + case XProcConstants.p_try => + children += parseTry(child) + case XProcConstants.p_variable => + children += parseVariable(child) + case XProcConstants.p_viewport => + children += parseViewport(child) + case XProcConstants.p_when => + children += parseWhen(child) + case XProcConstants.p_with_input => + children += parseWithInput(child) + case XProcConstants.p_with_option => + children += parseWithOption(child) + case XProcConstants.cx_loop => + children += parseForLoop(child) + case XProcConstants.cx_until => + children += parseUntilLoop(child) + case XProcConstants.cx_while => + children += parseWhileLoop(child) + + case _ => + children += parseAtomicStep(child) + } + } + case _ => + throw XProcException.xiThisCantHappen(s"Unexpected child node kind: ${child}") + } + } catch { + case ex: Exception => + _exceptions += ex + } + } + children.toList + } + + private def parseAtomicStep(node: XdmNode): XAtomicStep = { + val decl = new XAtomicStep(config, node.getNodeName) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseDeclareStep(node: XdmNode): XDeclareStep = { + val decl = new XDeclareStep(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseLibrary(node: XdmNode): XLibrary = { + val decl = new XLibrary(config, Option(node.getBaseURI)) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseCatch(node: XdmNode): XCatch = { + val decl = new XCatch(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseChoose(node: XdmNode): XChoose = { + val decl = new XChoose(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseDocument(node: XdmNode): XDocument = { + val decl = new XDocument(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseDocumentation(node: XdmNode): XDocumentation = { + val decl = new XDocumentation(config, nodeContent(node, false)) + decl.parse(node, List()) + decl + } + + private def parseEmpty(node: XdmNode): XEmpty = { + val decl = new XEmpty(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseFinally(node: XdmNode): XFinally = { + val decl = new XFinally(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseForEach(node: XdmNode): XForEach = { + val decl = new XForEach(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseForLoop(node: XdmNode): XForLoop = { + val decl = new XForLoop(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseUntilLoop(node: XdmNode): XForUntil = { + val decl = new XForUntil(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseWhileLoop(node: XdmNode): XForWhile = { + val decl = new XForWhile(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseGroup(node: XdmNode): XGroup = { + val decl = new XGroup(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseIf(node: XdmNode): XIf = { + val decl = new XIf(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseImport(node: XdmNode): Option[XImport] = { + val ihier = hier.imported(node) + if (ihier.isEmpty) { + var found = Option.empty[NodeHierarchy] + for (rehier <- hier.reimports) { + if (rehier.baseURI.isDefined + && rehier.baseURI.get == node.getBaseURI.resolve(node.getAttributeValue(XProcConstants._href))) { + found = Some(rehier) + } + } + if (found.isDefined && found.get.ximport.isDefined) { + return found.get.ximport + } else { + throw XProcException.xiThisCantHappen("Reimport that doesn't have an XImport?") + } + } + + val loader = new Loader(parser, ihier.get) + + _exceptions ++= loader.exceptions + + if (!ihier.get.useWhen(hier.root)) { + _exceptions += XProcException.xsInvalidPipeline("Root element use-when is false; no document", None) + } + + if (_exceptions.nonEmpty) { + throw _exceptions.head + } + + val lib = loader.library.get + + lib.builtinLibraries = parser.builtinLibraries + for (imp <- lib.children[XImport]) { + lib.addInScopeSteps(imp.library.inScopeSteps) + } + + lib.xelaborate() + _exceptions ++= lib.errors + + if (_exceptions.nonEmpty) { + throw _exceptions.head + } + + val decl = new XImport(config, lib) + decl.parse(node, parseChildren(node)) + ihier.get.ximport = decl + Some(decl) + } + + private def parseImportFunctions(node: XdmNode): XImportFunctions = { + val decl = new XImportFunctions(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseInline(node: XdmNode): XInline = { + val decl = new XInline(config, checkAttributes(nodeContent(node, false)), false) + decl.parse(node, List()) + decl.parseExpandText(node) + decl.parseExcludedUris(node) + decl + } + + private def nodeContent(node: XdmNode, includeNode: Boolean): XdmNode = { + val builder = new SaxonTreeBuilder(config) + + try { + builder.startDocument(Option(node.getBaseURI)) + } catch { + case _: IllegalStateException => + _exceptions += XProcException.xdInvalidURI(node.getUnderlyingNode.getBaseURI, None) + case ex: Exception => + _exceptions += ex + } + + if (includeNode) { + filterUseWhen(builder, node) + } else { + for (child <- node.axisIterator(Axis.CHILD).asScala) { + filterUseWhen(builder, child) + } + } + + builder.endDocument() + builder.result + } + + private def filterUseWhen(builder: SaxonTreeBuilder, node: XdmNode): Unit = { + node.getNodeKind match { + case XdmNodeKind.DOCUMENT => + for (child <- node.axisIterator(Axis.CHILD).asScala) { + filterUseWhen(builder, child) + } + case XdmNodeKind.ELEMENT => + if (useWhen(node)) { + val usewhen = if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { + XProcConstants._use_when + } else { + XProcConstants.p_use_when + } + var attrMap: AttributeMap = EmptyAttributeMap.getInstance() + val iter = node.axisIterator(Axis.ATTRIBUTE) + while (iter.hasNext) { + val attr = iter.next() + if (attr.getNodeName != usewhen) { + attrMap = attrMap.put(TypeUtils.attributeInfo(attr.getNodeName, attr.getStringValue)) + } + } + builder.addStartElement(node, attrMap) + for (child <- node.axisIterator(Axis.CHILD).asScala) { + filterUseWhen(builder, child) + } + builder.addEndElement() + } + case _ => + builder.addSubtree(node) + } + } + + private def parseInput(node: XdmNode): XInput = { + val decl = new XInput(config) + decl.parse(node, parseConnectionChildren(node)) + decl + } + + private def parseConnectionChildren(node: XdmNode): List[XArtifact] = { + var connection = false + var conncount = 0 + var empty = Option.empty[XdmNode] + var nonElementDecls = false + val implicitInlines = ListBuffer.empty[XdmNode] + + for (child <- node.axisIterator(Axis.CHILD).asScala) { + child.getNodeKind match { + case XdmNodeKind.ELEMENT => + if (useWhen(child)) { + child.getNodeName match { + case XProcConstants.p_inline => + connection = true + conncount += 1 + case XProcConstants.p_empty => + connection = true + conncount += 1 + empty = Some(child) + case XProcConstants.p_pipe => + connection = true + conncount += 1 + case XProcConstants.p_document => + connection = true + conncount += 1 + case XProcConstants.p_pipeinfo => + () + case XProcConstants.p_documentation => + () + case _ => + implicitInlines += child + conncount += 1 + } + } + case XdmNodeKind.COMMENT => + connection = true + nonElementDecls = true + conncount += 1 + case XdmNodeKind.TEXT => + if (child.getStringValue.trim() != "") { + connection = true + nonElementDecls = true + conncount += 1 + } + case XdmNodeKind.PROCESSING_INSTRUCTION => + connection = true + nonElementDecls = true + conncount += 1 + case _ => + throw XProcException.xiThisCantHappen(s"Connection children included ${child}") + } + } + + if (connection) { + if (conncount > 1 && empty.isDefined) { + throw XProcException.xsNoSiblingsOnEmpty(XdmLocation.from(empty.get)) + } + if (implicitInlines.nonEmpty) { + if (nonElementDecls) { + throw XProcException.xsInlineNotAllowed(XdmLocation.from(implicitInlines.head)) + } + throw XProcException.xsCantMixConnectionsWithImplicitInlines(implicitInlines.head.getNodeName, XdmLocation.from(implicitInlines.head)) + } + return parseChildren(node) + } + + val inlines = ListBuffer.empty[XInline] + for (child <- implicitInlines) { + val decl = new XInline(config, checkAttributes(nodeContent(child, true)), true) + decl.parse(node, List()) + decl.parseExpandText(node) + decl.parseExcludedUris(node) + inlines += decl + } + + inlines.toList + } + + private def checkAttributes(node: XdmNode): XdmNode = { + node.getNodeKind match { + case XdmNodeKind.DOCUMENT => + for (child <- node.axisIterator(Axis.CHILD).asScala) { + checkAttributes(child) + } + case XdmNodeKind.ELEMENT => + if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { + for (attr <- node.axisIterator(Axis.ATTRIBUTE).asScala) { + if (attr.getNodeName == XProcConstants._expand_text || attr.getNodeName == XProcConstants._inline_expand_text) { + if (attr.getStringValue != "true" && attr.getStringValue != "false") { + _exceptions += XProcException.xsInvalidExpandText(attr.getNodeName, attr.getStringValue, XdmLocation.from(node)) + } + } + } + } else { + for (attr <- node.axisIterator(Axis.ATTRIBUTE).asScala) { + if (attr.getNodeName == XProcConstants.p_expand_text || attr.getNodeName == XProcConstants.p_inline_expand_text) { + if (attr.getStringValue != "true" && attr.getStringValue != "false") { + _exceptions += XProcException.xsInvalidExpandText(attr.getNodeName, attr.getStringValue, XdmLocation.from(node)) + } + } + } + } + for (child <- node.axisIterator(Axis.CHILD).asScala) { + checkAttributes(child) + } + case _ => () + } + + node + } + + private def parseOption(node: XdmNode): XOption = { + if (node.getAttributeValue(XProcConstants._static) == "true") { + // We've already worked out this value while parsing the nodes + } + + val decl = new XOption(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseOtherwise(node: XdmNode): XOtherwise = { + val decl = new XOtherwise(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseOutput(node: XdmNode): XOutput = { + val decl = new XOutput(config) + decl.parse(node, parseConnectionChildren(node)) + decl + } + + private def parsePipe(node: XdmNode): XPipe = { + val decl = new XPipe(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parsePipeinfo(node: XdmNode): XPipeinfo = { + val decl = new XPipeinfo(config, nodeContent(node, false)) + decl.parse(node, List()) + decl + } + + private def parseTry(node: XdmNode): XTry = { + val decl = new XTry(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseVariable(node: XdmNode): XVariable = { + val decl = new XVariable(config) + decl.parse(node, parseConnectionChildren(node)) + decl + } + + private def parseViewport(node: XdmNode): XViewport = { + val decl = new XViewport(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseWhen(node: XdmNode): XWhen = { + val decl = new XWhen(config) + decl.parse(node, parseChildren(node)) + decl + } + + private def parseWithInput(node: XdmNode): XWithInput = { + val decl = new XWithInput(config) + decl.parse(node, parseConnectionChildren(node)) + decl + } + + private def parseWithOption(node: XdmNode): XWithOption = { + val decl = new XWithOption(config) + decl.parse(node, parseConnectionChildren(node)) + decl + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/NodeHierarchy.scala b/src/main/scala/com/xmlcalabash/model/xxml/NodeHierarchy.scala new file mode 100644 index 0000000..3a37c96 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/NodeHierarchy.scala @@ -0,0 +1,501 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.messages.Message +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.config.DocumentRequest +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XProcXPathExpression} +import com.xmlcalabash.util.{MediaType, S9Api, XdmLocation} +import net.sf.saxon.ma.map.{MapItem, MapType} +import net.sf.saxon.s9api.{Axis, QName, XdmMap, XdmNode, XdmNodeKind} +import org.slf4j.{Logger, LoggerFactory} + +import java.net.URI +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.jdk.CollectionConverters.IteratorHasAsScala + +object NodeHierarchy { + def newInstance(config: XMLCalabash, node: XdmNode): NodeHierarchy = { + config.staticStepsIndeterminate = true + + val hier = new NodeHierarchy(config, node, Option(node.getBaseURI), List()) + + var changed = false + if (hier.conditionalImports.nonEmpty) { + hier._conditionalImports.clear() + changed = hier.resolve() + while (changed) { + hier._conditionalImports.clear() + changed = hier.resolve() + } + if (hier.conditionalImports.nonEmpty) { + val node = hier.conditionalImports.head + throw XProcException.xsUseWhenDeadlock(hier.useWhenExpression(node).get, XdmLocation.from(node)) + } + } + + config.staticStepsIndeterminate = false + + changed = hier.resolve() + while (changed) { + changed = hier.resolve() + } + + hier.resolveDeadlocks() + + hier.unifyStatus() + + hier + } +} + +class NodeHierarchy private(config: XMLCalabash, node: XdmNode, val baseURI: Option[URI], ancestors: List[NodeHierarchy]) { + protected val logger: Logger = LoggerFactory.getLogger(this.getClass) + private val _imports = mutable.HashMap.empty[URI, NodeHierarchy] + private val _reimports = mutable.HashSet.empty[NodeHierarchy] + private val useWhenStatus = mutable.HashMap.empty[XdmNode, Option[Boolean]] + private val _knownSteps = mutable.HashSet.empty[QName] + private val _conditionalSteps = mutable.HashMap.empty[QName, mutable.HashSet[XdmNode]] + private val _conditionalImports = mutable.HashSet.empty[XdmNode] + private val _root = S9Api.documentElement(node) + private var changed = false + private var _ximport = Option.empty[XImport] + + if (_root.isEmpty) { + throw XProcException.xsNotAPipeline() + } + + if (root.getNodeName != XProcConstants.p_library && root.getNodeName != XProcConstants.p_declare_step) { + throw XProcException.xsNotAPipeline(root.getNodeName) + } + + recurse(root) + + def root: XdmNode = _root.get + def imports: List[NodeHierarchy] = _imports.values.toList + def reimports: Set[NodeHierarchy] = _reimports.toSet + def knownSteps: Set[QName] = _knownSteps.toSet + def conditionalSteps: Set[QName] = { + val steps = mutable.HashSet.empty[QName] ++ _conditionalSteps.keySet + steps.toSet + } + def conditionalImports: Set[XdmNode] = _conditionalImports.toSet + def useWhen(node: XdmNode): Boolean = { + if (useWhenStatus.contains(node)) { + (useWhenStatus(node).get) + } else { + true + } + } + + protected[xxml] def ximport: Option[XImport] = _ximport + protected[xxml] def ximport_=(xi: XImport): Unit = { + _ximport = Some(xi) + } + + def imported(node: XdmNode): Option[NodeHierarchy] = { + val href = if (Option(node.getAttributeValue(XProcConstants._href)).isDefined) { + if (Option(node.getBaseURI).isDefined) { + node.getBaseURI.resolve(node.getAttributeValue(XProcConstants._href)) + } else { + // If this wasn't absolute, we'd have thrown an exception in NodeHierarchy + new URI(node.getAttributeValue(XProcConstants._href)) + } + } else { + throw XProcException.xsMissingRequiredAttribute(XProcConstants._href, XdmLocation.from(node)) + } + + _imports.get(href) + } + + private def recurse(node: XdmNode): Unit = { + val useWhen = testUseWhen(node) + + if (useWhen.isEmpty && node.getNodeName == XProcConstants.p_import) { + _conditionalImports += node + } + + if (useWhen.getOrElse(false)) { + node.getNodeName match { + case XProcConstants.p_import => + processImport(node) + + case XProcConstants.p_option => + val context = new XMLStaticContext(node) + if (Option(node.getAttributeValue(XProcConstants._name)).isDefined) { + try { + val name = context.parseQName(node.getAttributeValue(XProcConstants._name)) + + if (Option(node.getAttributeValue(XProcConstants._static)).getOrElse("false") == "true") { + val value = staticOptionValue(node) + config.addStatic(name, value) + } else { + if (node.getParent.getNodeName == XProcConstants.p_library) { + throw XProcException.xsOptionMustBeStatic(name, XdmLocation.from(node)) + } + } + } catch { + case ex: XProcException => + // Map err:XD0015 to err:XS0087 in this case + if (ex.code == XProcException.errxd(15)) { + throw XProcException.xsOptionUndeclaredNamespace(node.getAttributeValue(XProcConstants._name), ex.location) + } else { + throw ex + } + case ex: Exception => + throw ex + } + } + + case _ => () + } + + for (child <- node.axisIterator(Axis.CHILD).asScala) { + if (child.getNodeKind == XdmNodeKind.ELEMENT) { + recurse(child) + } + } + } + } + + private def processImport(node: XdmNode): Unit = { + if (Option(node.getAttributeValue(XProcConstants._href)).isDefined) { + val uri = if (baseURI.isDefined) { + node.getBaseURI.resolve(node.getAttributeValue(XProcConstants._href)) + } else { + val hrefuri = new URI(node.getAttributeValue(XProcConstants._href)) + if (!hrefuri.isAbsolute) { + throw XProcException.xdInvalidURI(hrefuri.toString, None) + } + hrefuri + } + + var ancestor = Option.empty[NodeHierarchy] + for (hier <- ancestors) { + if (hier.baseURI.isDefined && hier.baseURI.get == uri) { + ancestor = Some(hier) + } + } + + if (_imports.contains(uri)) { + // nevermind, there's nothing gained from importing it twice at the same level + } else if (ancestor.isDefined) { + _reimports += ancestor.get + } else { + val newstack = ListBuffer.empty[NodeHierarchy] ++ ancestors + newstack += this + val request = new DocumentRequest(uri, MediaType.XML) + try { + val response = config.documentManager.parse(request) + if (response.contentType.xmlContentType) { + val hier = new NodeHierarchy(config, response.value.asInstanceOf[XdmNode], Some(uri), newstack.toList) + + if (hier.root.getNodeName == XProcConstants.p_declare_step + && Option(hier.root.getAttributeValue(XProcConstants._type)).isEmpty) { + throw XProcException.xsStepTypeRequired(XdmLocation.from(root)) + } + + _imports.put(uri, hier) + _conditionalImports ++= hier._conditionalImports + changed = true + } else { + throw XProcException.xsInvalidPipeline(s"Document is not XML: ${uri}", None) + } + } catch { + case ex: XProcException => + if (ex.code == XProcException.errxs(53)) { + throw ex + } else { + throw XProcException.xsImportFailed(uri, None) + } + case _: Exception => + throw XProcException.xsImportFailed(uri, None) + } + } + } + } + + private def useWhenExpression(node: XdmNode): Option[String] = { + if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { + Option(node.getAttributeValue(XProcConstants._use_when)) + } else { + Option(node.getAttributeValue(XProcConstants.p_use_when)) + } + } + + private def testUseWhen(node: XdmNode): Option[Boolean] = { + var context = Option.empty[StaticContext] + var stepType = Option.empty[QName] + if (node.getNodeName == XProcConstants.p_declare_step + && Option(node.getAttributeValue(XProcConstants._type)).isDefined) { + context = Some(new StaticContext(config, node)) + stepType = Some(context.get.parseQName(node.getAttributeValue(XProcConstants._type))) + if (_conditionalSteps.contains(stepType.get)) { + _conditionalSteps(stepType.get) -= node + } + } + if (node.getNodeName == XProcConstants.p_import) { + _conditionalImports -= node + } + + val usewhenexpr = useWhenExpression(node) + if (usewhenexpr.isEmpty) { + if (stepType.isDefined && pipelineStep(node).getOrElse(false)) { + _knownSteps += stepType.get + } + return Some(true) + } + + var useWhen = Option.empty[Boolean] + if (useWhenStatus.contains(node)) { + useWhen = useWhenStatus(node) + } + if (useWhen.isDefined) { + return useWhen + } + + if (context.isEmpty) { + context = Some(new StaticContext(config, node)) + } + + try { + val expr = new XProcXPathExpression(context.get, usewhenexpr.get) + val bindings = mutable.HashMap.empty[String, Message] + for ((name, value) <- config.staticOptions) { + bindings.put(name.getClarkName, value) + } + config.staticStepsAvailable = _knownSteps.toSet + useWhen = Some(config.expressionEvaluator.booleanValue(expr, List(), bindings.toMap, None)) + logger.debug(s"Use-when: ${useWhen.get}: ${usewhenexpr.get}") + changed = true + + if (node.getNodeName == XProcConstants.p_import) { + processImport(node) + } + } catch { + case ex: XProcException => + if (ex.code == XProcException.err_stepAvailableIndeterminate) { + useWhen = None + } else { + throw ex + } + case ex: Exception => + throw ex + } + useWhenStatus.put(node, useWhen) + + if (stepType.isDefined) { + if (useWhen.isEmpty) { + if (_conditionalSteps.contains(stepType.get)) { + _conditionalSteps(stepType.get) += node + } else { + val set = mutable.HashSet.empty[XdmNode] + set += node + _conditionalSteps.put(stepType.get, set) + } + } else { + if (useWhen.get && pipelineStep(node).getOrElse(false)) { + _knownSteps += stepType.get + } + } + } + + if (useWhen.isEmpty && node.getNodeName == XProcConstants.p_import) { + _conditionalImports += node + } + + useWhen + } + + private def resolve(): Boolean = { + changed = false + + for (ximport <- imports) { + _knownSteps ++= ximport.knownSteps + } + for (ximport <- _reimports) { + _knownSteps ++= ximport.knownSteps + } + + walk(_root.get) + + for (ximport <- imports) { + ximport.resolve() + changed = changed || ximport.changed + } + + changed + } + + private def walk(node: XdmNode): Unit = { + val useWhen = testUseWhen(node) + + if (!useWhen.getOrElse(false)) { + return + } + + if (node.getNodeName == XProcConstants.p_declare_step && Option(node.getAttributeValue(XProcConstants._type)).isDefined) { + val context = new StaticContext(config, node) + if (pipelineStep(node).getOrElse(false)) { + _knownSteps += context.parseQName(node.getAttributeValue(XProcConstants._type)) + } + } + if (node.getNodeName == XProcConstants.p_declare_step || node.getNodeName == XProcConstants.p_library) { + for (child <- node.axisIterator(Axis.CHILD).asScala) { + if (child.getNodeKind == XdmNodeKind.ELEMENT + && child.getNodeName == XProcConstants.p_declare_step + && Option(child.getAttributeValue(XProcConstants._type)).isDefined) { + val uw = testUseWhen(child) + if (uw.getOrElse(false)) { + val context = new StaticContext(config, child) + if (pipelineStep(child).getOrElse(false)) { + _knownSteps += context.parseQName(child.getAttributeValue(XProcConstants._type)) + } + } + } + } + } + + for (child <- node.axisIterator(Axis.CHILD).asScala) { + if (child.getNodeKind == XdmNodeKind.ELEMENT) { + walk(child) + } + } + } + + private def pipelineStep(node: XdmNode): Option[Boolean] = { + for (step <- node.axisIterator(Axis.CHILD).asScala) { + if (step.getNodeKind == XdmNodeKind.ELEMENT) { + step.getNodeName match { + case XProcConstants.p_input => () + case XProcConstants.p_output => () + case XProcConstants.p_option => () + case XProcConstants.p_documentation => () + case XProcConstants.p_pipeinfo => () + case XProcConstants.p_import => () + case XProcConstants.p_import_functions => () + case XProcConstants.p_declare_step => () + case _ => + val usewhenexpr = useWhenExpression(step) + if (usewhenexpr.isDefined) { + var useWhen = Option.empty[Boolean] + if (useWhenStatus.contains(step)) { + useWhen = useWhenStatus(step) + } + if (usewhenexpr.isDefined && useWhen.isEmpty) { + return None + } else { + if (useWhen.get) { + return useWhen + } + } + } else { + return Some(true) + } + } + } + } + Some(false) + } + + private def resolveDeadlocks(): Unit = { + walkDeadlocks(_root.get) + for (ximport <- imports) { + ximport.resolveDeadlocks() + } + } + + private def walkDeadlocks(node: XdmNode): Unit = { + val usewhenexpr = useWhenExpression(node) + var useWhen = Option.empty[Boolean] + if (useWhenStatus.contains(node)) { + useWhen = useWhenStatus(node) + } + if (useWhen.isEmpty && usewhenexpr.isDefined) { + throw XProcException.xsUseWhenDeadlock(usewhenexpr.get, XdmLocation.from(node)) + } else { + useWhen = Some(true) + } + + if (useWhen.getOrElse(false)) { + for (child <- node.axisIterator(Axis.CHILD).asScala) { + if (child.getNodeKind == XdmNodeKind.ELEMENT) { + walkDeadlocks(child) + } + } + } + } + + private def unifyStatus(): Unit = { + for (ximport <- imports) { + ximport.unifyStatus() + useWhenStatus ++= ximport.useWhenStatus + } + } + + private def staticOptionValue(node: XdmNode): XdmValueItemMessage = { + if (Option(node.getAttributeValue(XProcConstants._name)).isEmpty) { + throw XProcException.xsMissingRequiredAttribute(XProcConstants._name, XdmLocation.from(node)) + } + + val context = new XMLStaticContext(node) + val name = context.parseQName(node.getAttributeValue(XProcConstants._name)) + + if (Option(node.getAttributeValue(XProcConstants._required)).isDefined) { + if (node.getAttributeValue(XProcConstants._required) == "true") { + throw XProcException.xsRequiredAndStatic(name, XdmLocation.from(node)) + } + } + if (Option(node.getAttributeValue(XProcConstants._select)).isEmpty) { + throw XProcException.xsMissingRequiredAttribute(XProcConstants._select, XdmLocation.from(node)) + } + var value = if (config.options.contains(name)) { + new XdmValueItemMessage(config.options(name).value, XProcMetadata.ANY, config.options(name).context) + } else { + val select = node.getAttributeValue(XProcConstants._select) + val expr = new XProcXPathExpression(context, select) + val bindings = mutable.HashMap.empty[String, XdmValueItemMessage] + for ((name, value) <- config.staticOptions) { + bindings.put(name.getClarkName, value) + } + config.expressionEvaluator.value(expr, List(), bindings.toMap, None) + } + + var qnameKeys = false + val as = Option(node.getAttributeValue(XProcConstants._as)) + var declaredType = context.parseSequenceType(as, config.itemTypeFactory) + if (declaredType.isDefined) { + declaredType.get.getUnderlyingSequenceType.getPrimaryType match { + case map: MapType => + if (map.getKeyType.getPrimitiveItemType.getTypeName == XProcConstants.structured_xs_QName) { + // We have to lie about the type of maps with QName keys because we're + // going to allow users to put strings in there. + qnameKeys = true + declaredType = Some(context.parseFakeMapSequenceType(as.get, config.itemTypeFactory)) + } + case _ => () + } + + if (qnameKeys) { + value.item match { + case xmap: XdmMap => + val qnameMap = S9Api.forceQNameKeys(xmap.getUnderlyingValue, value.context) + value = new XdmValueItemMessage(qnameMap, value.metadata, value.context) + case xmap: MapItem => + val qnameMap = S9Api.forceQNameKeys(xmap, value.context) + value = new XdmValueItemMessage(qnameMap, value.metadata, value.context) + case _ => + throw XProcException.xiThisCantHappen(s"Non-map item has qnameKeys: ${value.item}") + } + } + + val tokens = XNameBinding.checkValueTokens(config, context, Option(node.getAttributeValue(XProcConstants._values))) + XNameBinding.promotedValue(config, name, declaredType, tokens, value) + } else { + value + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XArtifact.scala b/src/main/scala/com/xmlcalabash/model/xxml/XArtifact.scala new file mode 100644 index 0000000..7121219 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XArtifact.scala @@ -0,0 +1,728 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.Location +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, UniqueId, XProcConstants} +import com.xmlcalabash.util.{TypeUtils, URIUtils} +import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} +import net.sf.saxon.s9api.{Axis, QName, XdmNode, XdmNodeKind} +import org.slf4j.{Logger, LoggerFactory} + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.jdk.CollectionConverters.IteratorHasAsScala +import scala.reflect.ClassTag + +abstract class XArtifact(val config: XMLCalabash) { + protected val logger: Logger = LoggerFactory.getLogger(this.getClass) + protected var _synthetic = false + protected var _syntheticName = Option.empty[QName] + private var _tumble_id = Option.empty[String] + + private var _staticContext: XArtifactContext = _ + private var _xml_id = Option.empty[String] + private val _exceptions = ListBuffer.empty[Exception] + private val _attributes = mutable.HashMap.empty[QName, String] + private var _parent = Option.empty[XArtifact] + private val _children = ListBuffer.empty[XArtifact] + + def synthetic: Boolean = _synthetic + protected[xxml] def synthetic_=(synth: Boolean): Unit = { + _synthetic = synth + } + def syntheticName: Option[QName] = _syntheticName + protected[xxml] def syntheticName_=(name: QName): Unit = { + _syntheticName = Some(name) + } + + def nodeName: QName = { + if (_synthetic) { + if (_syntheticName.isDefined) { + _syntheticName.get + } else { + XProcConstants._synthetic + } + } else { + _staticContext.nodeName + } + } + def location: Option[Location] = _staticContext.location + def staticContext: XArtifactContext = _staticContext + protected[xxml] def staticContext_=(context: XArtifactContext): Unit = { + _staticContext = context + } + protected[xxml] def tumble_id: String = { + if (_tumble_id.isEmpty) { + _tumble_id = Some(s"!syn_${UniqueId.nextId}") + } + _tumble_id.get + } + protected[xxml] def tumble_id_=(id: String): Unit = { + _tumble_id = Some(id) + } + + def error(ex: Exception): Unit = { + ex match { + case ex: XProcException => + if (location.isDefined) { + _exceptions += ex.withLocation(location.get) + } else { + _exceptions += ex + } + case _ => + _exceptions += ex + } + // FIXME: + throw ex + } + + def exceptions: List[Exception] = { + val exlist = ListBuffer.empty[Exception] ++ _exceptions + for (child <- allChildren) { + exlist ++= child.exceptions + } + exlist.toList + } + + def parent: Option[XArtifact] = _parent + protected[xxml] def parent_=(parent: XArtifact): Unit = { + _parent = Some(parent) + } + + def root: XArtifact = { + var p: Option[XArtifact] = Some(this) + while (p.get.parent.isDefined) { + p = p.get.parent + } + p.get + } + + def attributes: Map[QName,String] = _attributes.toMap + + protected[xxml] def ancestor[T <: XArtifact](implicit tag: ClassTag[T]): Option[T] = { + this match { + case art: T => Some(art) + case _ => + if (parent.isDefined) { + parent.get.ancestor[T] + } else { + None + } + } + } + + def allChildren: List[XArtifact] = _children.toList + protected[xxml] def allChildren_=(children: List[XArtifact]): Unit = { + _children.clear() + for (child <- children) { + addChild(child) + } + } + + protected[xmlcalabash] def children[T <: XArtifact](implicit tag: ClassTag[T]): List[T] = { + allChildren.flatMap { + case art: T => Some(art) + case _ => None + } + } + + protected[xxml] def addChild(child: XArtifact): Unit = { + child.parent = this + _children += child + } + + private def findChild(find: XArtifact): Int = { + var idx = 0 + var found = false + for (child <- allChildren) { + if (found || (child eq find)) { + found = true + } else { + idx += 1 + } + } + idx + } + + protected[xxml] def removeChild(remove: XArtifact): Unit = { + _children.remove(findChild(remove)) + } + + protected[xxml] def replaceChild(remove: XArtifact, add: XArtifact): Unit = { + val idx = findChild(remove) + _children.remove(idx) + _children.insert(idx, add) + } + + protected[xxml] def insertBefore(insert: XArtifact, before: XArtifact): Unit = { + val idx = findChild(before) + insert.parent = this + _children.insert(idx, insert) + } + + def attr(name: QName): Option[String] = { + if (_attributes.contains(name)) { + val value = _attributes(name) + _attributes.remove(name) + Some(value) + } else { + None + } + } + + def parse(node: XdmNode, children: List[XArtifact]): Unit = { + _staticContext = new XArtifactContext(this, node) + _tumble_id = Some(tumbleId(node)) + + if (!synthetic) { + if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { + for (attr <- node.axisIterator(Axis.ATTRIBUTE).asScala) { + if (attr.getNodeName != XProcConstants._use_when) { + _attributes.put(attr.getNodeName, attr.getStringValue) + } + if (attr.getNodeName == XProcConstants._expand_text) { + if (attr.getStringValue != "true" && attr.getStringValue != "false") { + error(XProcException.xsInvalidExpandText(attr.getNodeName, attr.getStringValue, location)) + } + } + } + + } else { + for (attr <- node.axisIterator(Axis.ATTRIBUTE).asScala) { + if (attr.getNodeName != XProcConstants.p_use_when) { + _attributes.put(attr.getNodeName, attr.getStringValue) + } + if (attr.getNodeName == XProcConstants.p_expand_text) { + if (attr.getStringValue != "true" && attr.getStringValue != "false") { + error(XProcException.xsInvalidExpandText(attr.getNodeName, attr.getStringValue, location)) + } + } + } + } + } + + for (child <- children) { + child.parent = this + } + + _children ++= children + } + + private def tumbleId(node: XdmNode): String = { + var count = 1 + val iter = node.axisIterator(Axis.PRECEDING_SIBLING) + while (iter.hasNext) { + val child = iter.next() + if (child.getNodeKind == XdmNodeKind.ELEMENT) { + count += 1 + } + } + val parent = Option(node.getParent) + if (parent.isEmpty) { + "!" + } else { + val pid = tumbleId(parent.get) + if (pid == "!") { + s"$pid$count" + } else { + s"$pid.$count" + } + } + } + + protected def parsePipeAttribute(parent: XArtifact, pipe: String): List[XPipe] = { + val pipes = ListBuffer.empty[XPipe] + for (token <- pipe.trim.split("\\s+")) { + val pos = token.indexOf("@") + val pstr = if (pos < 0) { + token + } else { + token.substring(0, pos) + } + val sstr = if (pos < 0) { + "" + } else { + if (pos+1 == token.length) { + error(XProcException.xsInvalidPipeToken(token, location)) + } + token.substring(pos + 1) + } + + try { + val port = if (pstr == "") { + None + } else { + Some(staticContext.parseNCName(pstr)) + } + + val step = if (sstr == "") { + None + } else { + Some(staticContext.parseNCName(sstr)) + } + + pipes += new XPipe(parent, step, port) + } catch { + case _: XProcException => + error(XProcException.xsInvalidPipeToken(token, location)) + case ex: Exception => + error(ex) + } + } + pipes.toList + } + + protected[xxml] def checkEmptyAttributes(): Unit = { + val p_element = nodeName.getNamespaceURI == XProcConstants.ns_p + val globalAttributes = if (synthetic || p_element) { + List(XProcConstants.xml_base, XProcConstants._exclude_inline_prefixes, + XProcConstants._expand_text, XProcConstants._use_when) + } else { + List(XProcConstants.xml_base, XProcConstants.p_exclude_inline_prefixes, + XProcConstants.p_expand_text, XProcConstants.p_use_when) + } + for (name <- attributes.keySet) { + if (!globalAttributes.contains(name)) { + if (p_element && name.getNamespaceURI == XProcConstants.ns_p) { + error(XProcException.xsXProcNamespaceError(name, nodeName, None)) + } else { + this match { + case step: XStep => + if (step.stepDeclaration.isDefined && step.stepDeclaration.get.stepType.isDefined) { + error(XProcException.xsUndeclaredOption(step.stepDeclaration.get.stepType.get, name, location)) + } else { + error(XProcException.xsBadAttribute(name, None)) + } + case _ => + error(XProcException.xsBadAttribute(name, None)) + } + } + } + } + } + + protected[xxml] def checkAttributes(): Unit = { + val base = attr(XProcConstants.xml_base) + if (base.isDefined) { + val curbase = staticContext.baseURI.getOrElse(URIUtils.cwdAsURI) + staticContext.baseURI = curbase.resolve(base.get) + } + _xml_id = attr(XProcConstants.xml_id) + } + + protected[xxml] def validate(): Unit = { + println(s"ERROR: ${this} does not override validate() (${this.getClass.getName})") + } + + protected[xxml] def elaborateAttributes(): Unit = { + checkAttributes() + checkEmptyAttributes() + allChildren foreach { _.elaborateAttributes() } + } + + protected def hoistSourcesToPipes(): Unit = { + val moveMap = mutable.HashMap.empty[XArtifact,XPipe] + val stepMap = mutable.HashMap.empty[XArtifact,XArtifact] + val pstep: Option[XStep] = if (parent.isDefined) { + parent.get match { + case xs: XStep => Some(xs) + case _ => None + } + } else { + None + } + + for (child <- allChildren) { + child match { + case inline: XInline => + val step = new XInlineLoader(inline, this) + + // If the parent has dependencies, copy them onto the inline. + if (pstep.isDefined) { + step.dependsOn ++= pstep.get.dependsOn + } + + val pipe = new XPipe(this, Some(step.tumble_id), Some("result")) + moveMap.put(inline, pipe) + stepMap.put(pipe, step) + case doc: XDocument => + val step = new XDocumentLoader(doc, this) + + // If the parent has dependencies, copy them onto the inline. + if (pstep.isDefined) { + step.dependsOn ++= pstep.get.dependsOn + } + + val pipe = new XPipe(this, Some(step.tumble_id), Some("result")) + moveMap.put(doc, pipe) + stepMap.put(pipe, step) + case empty: XEmpty => + val step = new XEmptyLoader(this) + val pipe = new XPipe(this, Some(step.tumble_id), Some("result")) + moveMap.put(empty, pipe) + stepMap.put(pipe, step) + case _: XPipe => + () + case _ => + child.hoistSourcesToPipes() + } + } + + if (moveMap.isEmpty) { + return + } + + val portName = this match { + case port: XPort => + port.port + case _ => "" + } + + // Sometimes we're hoisting from within a p:with-input to the grandparent, + // sometimes we're hoisting from within a p:variable to the parent + var insertChild = this + var insertParent = parent + var found = false + while (!found && insertParent.isDefined) { + insertParent.get match { + case _: XTry => + // Only TryCatchBranches allowed + () + case _: XChoose => + // Only ChooseBranches allowed + () + case _: XWhen => + // Make sure we hoist any inputs for the condition outside the when + found = portName != "condition" + case _: XLoopingStep => + // Make sure we hoist any inputs for the loop outside the loop + found = portName != "#anon" + case _: XContainer => + found = true + case _ => + () + } + if (!found) { + insertChild = insertParent.get + insertParent = insertChild.parent + } + } + + if (!found) { + throw XProcException.xiThisCantHappen("Failed to find container ancestor for hoisting?") + } + + for ((child, pipe) <- moveMap) { + replaceChild(child, pipe) + val ipipe = stepMap(pipe) + insertParent.get.insertBefore(ipipe, insertChild) + } + } + + protected[xxml] def ancestorContainer: Option[XContainer] = { + var p: Option[XArtifact] = Some(this) + while (p.isDefined) { + p.get match { + case cont: XContainer => + return Some(cont) + case _ => () + } + p = p.get.parent + } + None + } + + protected[xxml] def ancestorStep: Option[XStep] = { + var p: Option[XArtifact] = Some(this) + while (p.isDefined) { + p.get match { + case step: XDeclareStep => + return Some(step) + case step: XStep => + return Some(step) + case _ => () + } + p = p.get.parent + } + None + } + + protected[xxml] def ancestorNode: Option[XArtifact] = { + var p: Option[XArtifact] = Some(this) + while (p.isDefined) { + p.get match { + case _: XStep => return p + case _: XVariable => return p + case _: XOption => return p + case _ => () + } + p = p.get.parent + } + None + } + + protected[xxml] def ancestorOf(step: XArtifact): Boolean = { + var p: Option[XArtifact] = Some(step) + while (p.isDefined) { + if (p.get eq this) { + return true + } + p = p.get.parent + } + false + } + + protected[xxml] def stepDeclaration: Option[XDeclareStep] = { + val dstep = ancestor[XStep] + if (dstep.isEmpty) { + return None + } + + dstep.get match { + case decl: XDeclareStep => + Some(decl) + case atomic: XAtomicStep => + val dcontainer = ancestor[XDeclContainer] + if (dcontainer.isDefined) { + dcontainer.get.findDeclaration(atomic.stepType) + } else { + None + } + case _ => + None + } + } + + protected[xxml] def elaborateSyntacticSugar(): Unit = { + for (child <- allChildren) { + child.elaborateSyntacticSugar() + } + } + + protected[xxml] def computeReadsFrom(): Unit = { + for (child <- allChildren) { + child.computeReadsFrom() + } + } + + protected[xxml] def validateExplicitConnections(href: Option[String], pipe: Option[String]): List[XArtifact] = { + if (href.isDefined && pipe.isDefined) { + error(XProcException.xsPipeAndHref(None)) + } + + val pipeOk = if (parent.isDefined) { + parent.get match { + case _: XInput => false + case _ => true + } + } else { + true + } + + var seenEmpty = false + val newChildren = ListBuffer.empty[XArtifact] + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case empty: XEmpty => + if (href.isDefined) { + error(XProcException.xsHrefAndOtherSources(None)) + } + if (pipe.isDefined) { + error(XProcException.xsPipeAndOtherSources(None)) + } + if (seenEmpty) { + error(XProcException.xsNoSiblingsOnEmpty(location)) + } + empty.validate() + newChildren += empty + seenEmpty = true + case doc: XDocument => + if (href.isDefined) { + error(XProcException.xsHrefAndOtherSources(None)) + } + if (pipe.isDefined) { + error(XProcException.xsPipeAndOtherSources(None)) + } + if (seenEmpty) { + error(XProcException.xsNoSiblingsOnEmpty(location)) + } + doc.validate() + newChildren += doc + case inline: XInline => + if (href.isDefined) { + error(XProcException.xsHrefAndOtherSources(None)) + } + if (pipe.isDefined) { + error(XProcException.xsPipeAndOtherSources(None)) + } + if (seenEmpty) { + error(XProcException.xsNoSiblingsOnEmpty(location)) + } + inline.validate() + newChildren += inline + case xpipe: XPipe => + if (pipeOk) { + if (href.isDefined) { + error(XProcException.xsHrefAndOtherSources(None)) + } + if (pipe.isDefined) { + error(XProcException.xsPipeAndOtherSources(None)) + } + if (seenEmpty) { + error(XProcException.xsNoSiblingsOnEmpty(location)) + } + xpipe.validate() + newChildren += xpipe + } else { + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + if (exceptions.isEmpty) { + if (href.isDefined) { + val doc = new XDocument(this, href.get) + doc.validate() + newChildren += doc + if (seenEmpty) { + error(XProcException.xsNoSiblingsOnEmpty(location)) + } + } + if (pipe.isDefined) { + for (pipe <- parsePipeAttribute(this, pipe.get)) { + pipe.validate() + newChildren += pipe + } + if (seenEmpty) { + error(XProcException.xsNoSiblingsOnEmpty(location)) + } + } + } + + newChildren.toList + } + + protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + var curdrp = initial + for (child <- allChildren) { + curdrp = child.elaborateDefaultReadablePort(curdrp) + } + curdrp + } + + protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + var curcontext = initial + + staticContext = staticContext.withConstants(initial) + + for (child <- allChildren) { + child match { + case decl: XDeclareStep => + decl.elaborateNameBindings(curcontext.onlyStatics) + case _ => + curcontext = child.elaborateNameBindings(curcontext) + } + } + + curcontext + } + + protected[xxml] def elaborateScopedFeatures(): Unit = { + for (child <- allChildren) { + child.elaborateScopedFeatures() + } + } + + protected[xxml] def elaboratePortConnections(): Unit = { + for (child <- allChildren) { + child.elaboratePortConnections() + } + } + + protected[xxml] def elaborateDependsConnections(inScopeSteps: Map[String, XStep]): Unit = { + for (child <- children[XStep]) { + child.elaborateDependsConnections(inScopeSteps) + } + } + + protected[xxml] def elaborateDynamicVariables(): Unit = { + allChildren foreach { _.elaborateDynamicVariables() } + } + + protected[xxml] def elaborateValidatePortConnections(ports: XPortBindingContext): Unit = { + var curports = ports + + this match { + case cont: XContainer => + curports = curports.withContainer(cont) + case _ => + () + } + + // Remove "irrelevant" inputs after we've validated them + val irrelevant = ListBuffer.empty[XArtifact] + for (child <- allChildren) { + child.elaborateValidatePortConnections(curports) + child match { + case port: XPort => + if (port.irrelevant) { + irrelevant += port + } + case _ => () + } + } + for (child <- irrelevant) { + if (child.exceptions.isEmpty) { + removeChild(child) + } + } + } + + def dump: XdmNode = { + val sb = new SaxonTreeBuilder(config) + sb.startDocument(staticContext.baseURI) + dumpTree(sb) + sb.endDocument() + sb.result + } + + def dumpTree(sb: SaxonTreeBuilder): Unit = { + throw XProcException.xiThisCantHappen("Don't know how to dump an artifact") + } + + def dumpTree(sb: SaxonTreeBuilder, name: String, attr: Map[String,Option[Any]]): Unit = { + dumpTree(sb, name, attr, None) + } + + def dumpTree(sb: SaxonTreeBuilder, name: String, attr: Map[String,Option[Any]], text: String): Unit = { + dumpTree(sb, name, attr, Some(text)) + } + + private def dumpTree(sb: SaxonTreeBuilder, name: String, attr: Map[String,Option[Any]], text: Option[String]): Unit = { + var amap: AttributeMap = EmptyAttributeMap.getInstance() + for ((name, value) <- attr) { + if (value.isDefined) { + amap = amap.put(TypeUtils.attributeInfo(staticContext.parseQName(name), value.get.toString)) + } + } + if (_synthetic) { + amap = amap.put(TypeUtils.attributeInfo(XProcConstants._synthetic, "true")) + } + + if (!attr.contains("name") || attr("name").isEmpty || attr("name").get != tumble_id) { + amap = amap.put(TypeUtils.attributeInfo(XProcConstants._tumble_id, tumble_id)) + } + + sb.addStartElement(staticContext.parseQName(name), amap) + if (text.isDefined) { + sb.addText(text.get) + } + allChildren foreach { child => child.dumpTree(sb) } + sb.addEndElement() + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XArtifactContext.scala b/src/main/scala/com/xmlcalabash/model/xxml/XArtifactContext.scala new file mode 100644 index 0000000..b618ff6 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XArtifactContext.scala @@ -0,0 +1,52 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.Location +import com.xmlcalabash.util.{MinimalStaticContext, S9Api, XdmLocation} +import net.sf.saxon.s9api.{QName, XdmNode} + +class XArtifactContext(val artifact: XArtifact, nodeName: QName, location: Option[Location]) extends XMLStaticContext(nodeName, location) { + def this(artifact: XArtifact, node: XdmNode) = { + this(artifact, node.getNodeName, XdmLocation.from(node)) + _inscopeNamespaces ++= S9Api.inScopeNamespaces(node) + } + + def this(artifact: XArtifact, ctx: MinimalStaticContext, name: QName) = { + this(artifact, name, ctx.location) + } + + def constants: List[XNameBinding] = _inscopeConstants.toList + + def withConstant(const: XNameBinding): XArtifactContext = { + val newContext = new XArtifactContext(artifact, nodeName, location) + newContext._inscopeConstants ++= _inscopeConstants + newContext._inscopeConstants += const + newContext._inscopeNamespaces ++= _inscopeNamespaces + newContext._baseURI = _baseURI + newContext._location = _location + newContext + } + + def withConstants(constants: List[XNameBinding]): XArtifactContext = { + val newContext = new XArtifactContext(artifact, nodeName, location) + newContext._inscopeConstants ++= _inscopeConstants + newContext._inscopeConstants ++= constants + newContext._inscopeNamespaces ++= _inscopeNamespaces + newContext._baseURI = _baseURI + newContext._location = _location + newContext + } + + def withConstants(bcontext: XNameBindingContext): XArtifactContext = { + if (bcontext.inScopeConstants.isEmpty) { + return this + } + + val newContext = new XArtifactContext(artifact, nodeName, location) + newContext._inscopeConstants ++= _inscopeConstants + newContext._inscopeConstants ++= bcontext.inScopeConstants.values + newContext._inscopeNamespaces ++= _inscopeNamespaces + newContext._baseURI = _baseURI + newContext._location = _location + newContext + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XAtomicStep.scala b/src/main/scala/com/xmlcalabash/model/xxml/XAtomicStep.scala new file mode 100644 index 0000000..db5491c --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XAtomicStep.scala @@ -0,0 +1,365 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.runtime.{StepExecutable, StepProxy, StepRunner, StepWrapper, XMLCalabashRuntime, XmlStep} +import com.xmlcalabash.util.MediaType +import net.sf.saxon.s9api.QName + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XAtomicStep(config: XMLCalabash, val stepType: QName) extends XStep(config) { + _type = Some(stepType) + + def this(parentStep: XContainer, stepType: QName) = { + this(parentStep.config, stepType) + staticContext = parentStep.staticContext + parent = parentStep + _synthetic = true + _syntheticName = Some(stepType) + } + + override def primaryOutput: Option[XPort] = { + children[XWithOutput] find { + _.primary + } + } + + // ======================================================================================= + // ======================================================================================= + // ======================================================================================= + // ======================================================================================= + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + if (ancestor[XDeclContainer].get.findDeclaration(stepType).isEmpty) { + return + } + + val decl = stepDeclaration.get + + val seenPorts = mutable.HashSet.empty[String] + val seenOptions = mutable.HashSet.empty[QName] + val newChildren = ListBuffer.empty[XArtifact] + + for (input <- children[XWithInput]) { + input.validate() + newChildren += input + if (!input.portSpecified) { + val idecl = decl.children[XInput] find { _.primary } + if (idecl.isDefined) { + input.port = idecl.get.port + } else { + error(XProcException.xsNoPrimaryInputPort(stepType, location)) + return + } + } + + if (seenPorts.contains(input.port)) { + input.error(XProcException.xsDupWithInputPort(input.port, None)) + } else { + if (decl.inputPorts.contains(input.port)) { + seenPorts += input.port + } else { + input.error(XProcException.xsNoSuchPort(input.port, stepType, None)) + } + } + } + + for (input <- decl.children[XInput]) { + if (!seenPorts.contains(input.port)) { + seenPorts += input.port + val xwi = new XWithInput(this, input.port) + newChildren += xwi + xwi.validate() + } + } + + // Configure the XWithInput like it's XInput declaration + for (input <- children[XWithInput]) { + val idecl = decl.children[XInput] find { _.port == input.port } + input.sequence = idecl.get.sequence + input.contentTypes = idecl.get.contentTypes + input.primary = idecl.get.primary + } + + if (children[XWithOption].nonEmpty) { + val input = new XWithInput(this, "#bindings") + input.primary = false + input.sequence = true + input.contentTypes = MediaType.MATCH_ANY + input.validate() + newChildren += input + } + + for (output <- decl.outputs) { + seenPorts += output.port + val xwo = new XWithOutput(this, output.port) + xwo.validate() + newChildren += xwo + } + + for (option <- children[XWithOption]) { + option.validate() + if (seenOptions.contains(option.name)) { + option.error(XProcException.xsDupWithOptionName(option.name, None)) + } else { + seenOptions += option.name + } + newChildren += option + } + + for (opt <- decl.options) { + if (!seenOptions.contains(opt.name)) { + if (opt.required) { + error(XProcException.xsMissingRequiredOption(opt.name, location)) + } + val expr = opt.select.getOrElse("()") + val xwo = new XWithOption(this, opt.name, None, Some(expr)) + newChildren += xwo + } + } + + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XWithInput => () + case _: XWithOutput => () + case _: XWithOption => () + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + allChildren = newChildren.toList + } + + // ======================================================================================= + // ======================================================================================= + // ======================================================================================= + // ======================================================================================= + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + + val decl = stepDeclaration + if (decl.isEmpty) { + if (stepType.getNamespaceURI == XProcConstants.ns_p) { + error(XProcException.xsElementNotAllowed(stepType, location)) + } else { + error(XProcException.xsMissingDeclaration(stepType, location)) + } + return + } + + for (name <- attributes.keySet) { + if (decl.get.option(name).isDefined) { + syntheticOption(name, attr(name).get) + } else { + error(XProcException.xsUndeclaredOption(stepType, name, location)) + } + } + } + + /* + override def elaborateDeclarations(inScopeNames: Set[String]): Unit = { + val seenPorts = mutable.HashSet.empty[String] + val seenOptions = mutable.HashSet.empty[QName] + val newChildren = ListBuffer.empty[XArtifact] + + val skeleton = declarationContainer.findSkeleton(stepType).get + + for (input <- children[XWithInput]) { + if (!input.portSpecified) { + val idecl = skeleton.decl.children[XInput] find { + _.primary + } + if (idecl.isDefined) { + input.port = idecl.get.port + } else { + error(XProcException.xsNoPrimaryInputPort(stepType, location)) + return + } + } + if (seenPorts.contains(input.port)) { + input.error(XProcException.xsDupWithInputPort(input.port, None)) + newChildren += input + } else { + if (skeleton.inputs.contains(input.port)) { + seenPorts += input.port + input.elaborateDeclarations() + newChildren += input + } else { + input.error(XProcException.xsNoSuchPort(input.port, stepType, None)) + newChildren += input // make sure we don't loose the exception! + } + } + } + + for (port <- skeleton.inputs) { + if (!seenPorts.contains(port)) { + seenPorts += port + val input = new XWithInput(this, port) + newChildren += input + input.elaborateDeclarations() + } + } + + if (children[XWithOption].nonEmpty) { + val input = new XWithInput(this, "#bindings") + input.primary = false + input.sequence = true + input.contentTypes = MediaType.MATCH_ANY + input.elaborateDeclarations() + newChildren += input + } + + for (port <- skeleton.outputs) { + seenPorts += port + val output = new XWithOutput(this, port) + output.elaborateDeclarations() + newChildren += output + } + + for (option <- children[XWithOption]) { + if (seenOptions.contains(option.name)) { + option.error(XProcException.xsDupWithOptionName(option.name, None)) + } else { + seenOptions += option.name + option.elaborateDeclarations() + } + newChildren += option + } + + for (name <- skeleton.options) { + if (!seenOptions.contains(name)) { + val odecl = skeleton.decl.option(name).get + if (odecl.required) { + throw XProcException.xsMissingRequiredOption(name, location) + } + val expr = odecl.select.getOrElse("()") + val opt = new XWithOption(this, name, None, Some(expr)) + newChildren += opt + } + } + + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XWithInput => () + case _: XWithOption => () + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + allChildren = newChildren.toList + } +*/ + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + super.elaborateDefaultReadablePort(initial) + val drp = children[XWithOutput] find { + _.primary + } + drp + } + + override protected[xxml] def elaborateValidatePortConnections(ports: XPortBindingContext): Unit = { + super.elaborateValidatePortConnections(ports) + + val decl = stepDeclaration.get + + for (child <- children[XWithInput]) { + if (!child.port.startsWith("#depends_") && child.port != "#bindings") { + if (child.children[XDataSource].isEmpty) { + val input = decl.children[XInput] find { _.port == child.port } + if (input.get.defaultInputs.nonEmpty) { + child.allChildren = input.get.defaultInputs + } else { + error(XProcException.xsUnconnectedInputPort(stepName, child.port, location)) + } + } + } + } + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + var bcontext = initial + for (child <- allChildren) { + bcontext = child.elaborateNameBindings(bcontext) + } + + staticContext = staticContext.withConstants(bcontext) + + initial + } + + override protected def elaborateDynamicOptions(): Unit = { + children[XWithOption] foreach { _.elaborateDynamicOptions() } + val xbind = children[XWithInput] find { _.port == "#bindings" } + if (xbind.isDefined) { + if (xbind.get.allChildren.isEmpty) { + removeChild(xbind.get) + } + } + } + + // ======================================================================================= + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[ContainerStart] + val impl = stepImplementation + impl.configure(config, stepType, name, None) + + val proxy = new StepProxy(runtime, stepType, impl, staticContext) + runtime.addNode(this, start.addAtomic(proxy, stepType.toString)) + } + + protected def stepImplementation: StepExecutable = { + val decl = stepDeclaration + if (decl.isEmpty) { + throw XProcException.xsMissingDeclaration(stepType, location) + } + if (decl.get.atomic) { + if (decl.get.implementationClass.isDefined) { + val implClass = decl.get.implementationClass.get + val klass = Class.forName(implClass).getDeclaredConstructor().newInstance() + klass match { + case step: XmlStep => + new StepWrapper(step, decl.get) + case _ => + throw XProcException.xiStepImplementationError(s"Class does not implement an XmlStep: ${stepType}", location) + } + } else { + throw XProcException.xiStepImplementationError(s"No implementation for ${stepType}", location); + } + } else { + new StepRunner(decl.get) + } + } + + // ======================================================================================= + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("type", Some(stepType.getEQName)) + attr.put("name", Some(stepName)) + dumpTree(sb, "p:atomic-step", attr.toMap) + } + + override def toString: String = { + if (stepName != tumble_id) { + s"${stepType}(${stepName};${tumble_id})" + } else { + s"${stepType}(${stepName})" + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XCatch.scala b/src/main/scala/com/xmlcalabash/model/xxml/XCatch.scala new file mode 100644 index 0000000..bdd9855 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XCatch.scala @@ -0,0 +1,54 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{Node, TryCatchStart} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.XMLCalabashRuntime +import net.sf.saxon.s9api.QName + +import scala.collection.mutable + +class XCatch(config: XMLCalabash) extends XTryCatchBranch(config) { + private val _codes = mutable.HashSet.empty[QName] + + def codes: Set[QName] = _codes.toSet + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + if (attributes.contains(XProcConstants._code)) { + val str = attr(XProcConstants._code).get + var repeated = Option.empty[QName] + for (code <- str.split("\\s+")) { + try { + val qname = staticContext.parseQName(code) + if (repeated.isEmpty && _codes.contains(qname)) { + repeated = Some(qname) + } + _codes += qname + } catch { + case _: Exception => + error(XProcException.xsCatchInvalidCode(code, None)) + } + } + if (repeated.isDefined) { + error(XProcException.xsCatchRepeatedCode(repeated.get, None)) + } + } + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + var curdrp: Option[XPort] = children[XInput] find { _.primary == true } + for (child <- allChildren) { + curdrp = child.elaborateDefaultReadablePort(curdrp) + } + initial + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[TryCatchStart] + val node = start.addCatch(stepName, _codes.toList, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XChoose.scala b/src/main/scala/com/xmlcalabash/model/xxml/XChoose.scala new file mode 100644 index 0000000..5e9d568 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XChoose.scala @@ -0,0 +1,215 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.XMLCalabashRuntime +import com.xmlcalabash.util.MediaType + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XChoose(config: XMLCalabash) extends XContainer(config) { + + def this(parentStep: XArtifact) = { + this(parentStep.config) + staticContext = parentStep.staticContext + parent = parentStep + synthetic = true + syntheticName = XProcConstants.p_choose + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + var seenWithInput = false + var seenWhen = false; + var seenOtherwise = false + + //val newScope = checkStepNameScoping(inScopeNames) + + val newChildren = ListBuffer.empty[XArtifact] + for (child <- allChildren) { + child.validate() + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XOutput =>() + case input: XWithInput => + if (seenWithInput) { + error(XProcException.xsInvalidPipeline("A p:choose can have at most one p:with-input", location)) + } + if (seenWhen || seenOtherwise) { + error(XProcException.xsInvalidPipeline("A p:with-input cannot follow p:when or p:otherwise in p:choose", location)) + } + seenWithInput = true + newChildren += input + case when: XWhen => + if (seenOtherwise) { + error(XProcException.xsInvalidPipeline("A p:when cannot follow p:otherwise in p:choose", location)) + } + seenWhen = true + newChildren += when + case otherwise: XOtherwise => + if (seenOtherwise) { + error(XProcException.xsInvalidPipeline("A p:choose can have at most one p:otherwise", location)) + } + seenOtherwise = true + newChildren += otherwise + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + if (!seenWhen && !seenOtherwise) { + error(XProcException.xsMissingWhen(None)) + } + + allChildren = newChildren.toList + + if (exceptions.nonEmpty) { + return + } + + val branch = children[XChooseBranch].head + if (children[XOtherwise].isEmpty) { + val other = new XOtherwise(this) + addChild(other) + + val identity = new XAtomicStep(other, XProcConstants.p_identity) + other.addChild(identity) + + val wi = new XWithInput(identity, "source") + identity.addChild(wi) + + if (branch.primaryOutput.isEmpty) { + val sink = new XAtomicStep(other, XProcConstants.p_sink) + other.addChild(sink) + } else { + val xout = new XOutput(other, Some(branch.primaryOutput.get.port)) + xout.primary = true + xout.sequence = true + other.insertBefore(xout, identity) + } + + other.validate() + } + + // Make sure all of the branches have consistent primary output ports + var cprimary = Option.empty[String] + for (branch <- children[XChooseBranch]) { + for (output <- branch.children[XOutput]) { + if (output.primary) { + cprimary = Some(output.port) + } + } + } + + val boutputs = mutable.HashMap.empty[String, XOutput] + for (branch <- children[XChooseBranch]) { + var bprimary = Option.empty[String] + for (output <- branch.children[XOutput]) { + if (cprimary.isDefined && output.primary && cprimary.get != output.port) { + error(XProcException.xsBadChooseOutputs(output.port, cprimary.get, location)) + } else { + if (boutputs.contains(output.port)) { + if (boutputs(output.port).primary != output.primary) { + error(XProcException.xsBadChooseOutputs(output.port, location)) + } + } else { + boutputs.put(output.port, output) + } + } + if (output.primary) { + bprimary = Some(output.port) + } + } + if (cprimary.isDefined != bprimary.isDefined) { + if (cprimary.isDefined) { + error(XProcException.xsBadChooseOutputs(cprimary.get, location)) + } else { + error(XProcException.xsBadChooseOutputs(bprimary.get, location)) + } + } + } + + for (port <- boutputs.keySet) { + val oport = if (port == "") { + None + } else { + Some(port) + } + val output = new XOutput(this, oport) + output.primary = boutputs(port).primary + output.sequence = true + output.contentTypes = MediaType.MATCH_ANY + insertBefore(output, allChildren.head) + + for (branch <- children[XChooseBranch]) { + val out = branch.children[XOutput] find { _.port == port } + if (out.isDefined) { + val pipe = new XPipe(output, branch.stepName, port) + output.addChild(pipe) + } + } + } + } + + override def elaboratePortConnections(): Unit = { + if (_drp.isDefined && children[XWithInput].isEmpty) { + val input = new XWithInput(config) + input.staticContext = staticContext + input.parent = this + val pipe = new XPipe(_drp.get) + pipe.parent = input + input.addChild(pipe) + insertBefore(input, allChildren.head) + } + + super.elaboratePortConnections() + + // The with-input isn't needed anymore + val input = children[XWithInput].headOption + if (input.isDefined) { + input.get.irrelevant = true + } + } + + override def publicPipeConnections: Map[String,XPort] = { + Map() + } + + override def privatePipeConnections: Map[String,XPort] = { + val ports = mutable.HashMap.empty[String,XPort] + + for (input <- children[XInput]) { + val port = input.port + ports.put(s"${name}/${port}", input) + } + + for (child <- children[XStep]) { + val name = child.stepName + for (output <- child.children[XOutput]) { + val port = output.port + ports.put(s"${name}/${port}", output) + } + for (output <- child.children[XWithOutput]) { + val port = output.port + ports.put(s"${name}/${port}", output) + } + } + + // There are no option or variable children of p:choose + + ports.toMap + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[ContainerStart] + val node = start.addChoose(stepName) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XChooseBranch.scala b/src/main/scala/com/xmlcalabash/model/xxml/XChooseBranch.scala new file mode 100644 index 0000000..c744b71 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XChooseBranch.scala @@ -0,0 +1,70 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ChooseStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.model.util.SaxonTreeBuilder +import com.xmlcalabash.runtime.params.XPathBindingParams +import com.xmlcalabash.runtime.{XMLCalabashRuntime, XProcXPathExpression} + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +abstract class XChooseBranch(config: XMLCalabash) extends XContainer(config) { + protected var _test = "" + protected var _collection = false + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + var curdrp = initial + for (child <- allChildren) { + curdrp = child.elaborateDefaultReadablePort(curdrp) + } + initial + } + + protected def orderChildren(): Unit = { + if (exceptions.nonEmpty) { + return + } + val newChildren = ListBuffer.empty[XArtifact] + newChildren ++= children[XWithInput] + newChildren ++= children[XOutput] + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XWithInput => () + case _: XOutput => () + case _ => + newChildren += child + } + } + allChildren = newChildren.toList + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[ChooseStart] + + val params = new XPathBindingParams(_collection) + val testExpr = new XProcXPathExpression(staticContext, _test, None, None, Some(params)) + + val node = start.addWhen(testExpr, stepName, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("name", Some(stepName)) + + if (this.isInstanceOf[XWhen]) { + attr.put("test", Some(_test)) + } + + if (_collection) { + attr.put("collection", Some(_collection)) + } + dumpTree(sb, nodeName.toString, attr.toMap) + } + +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XContainer.scala b/src/main/scala/com/xmlcalabash/model/xxml/XContainer.scala new file mode 100644 index 0000000..6dc584c --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XContainer.scala @@ -0,0 +1,373 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.steps.{Manifold, PortCardinality, PortSpecification} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.SaxonTreeBuilder +import com.xmlcalabash.util.MediaType + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +abstract class XContainer(config: XMLCalabash) extends XStep(config) { + protected var _drp = Option.empty[XPort] + + def containerManifold: Manifold = { + val spec = mutable.HashMap.empty[String, PortCardinality] + for (output <- outputs) { + if (output.sequence) { + spec.put(output.port, PortCardinality.ZERO_OR_MORE) + } else { + spec.put(output.port, PortCardinality.EXACTLY_ONE) + } + } + new Manifold(Manifold.WILD, new PortSpecification(spec.toMap)) + } + + override def primaryOutput: Option[XPort] = { + children[XOutput] find { _.primary } + } + + protected def constructDefaultOutput(): Unit = { + val firstStep = children[XStep].head + val step = children[XStep].last + val pout = step.primaryOutput + if (pout.isEmpty) { + return // There isn't a default putput + } + + if (children[XOutput].nonEmpty) { + val implicitPrimary = children[XOutput].length == 1 + for (xout <- children[XOutput]) { + checkDefaultOutput(pout.get, xout, implicitPrimary) + } + return + } + + val output = this match { + case _: XViewport => + new XOutput(this, Some("result")) + case _ => + new XOutput(this, None) + } + output.primary = true + output.sequence = pout.get.sequence + output.contentTypes = pout.get.contentTypes + + if (Option(firstStep).isDefined) { + insertBefore(output, firstStep) + } else { + addChild(output) + } + + checkDefaultOutput(pout.get, output, implicitPrimary=true) + } + + private def checkDefaultOutput(stepOut: XPort, output: XOutput, implicitPrimary: Boolean): Unit = { + if (implicitPrimary && !output.primarySpecified) { + output.primary = true + } + + if (!output.primary || output.children[XDataSource].nonEmpty) { + return + } + + val pipe = new XPipe(stepOut) + output.addChild(pipe) + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + var curdrp = initial + for (child <- allChildren) { + curdrp = child.elaborateDefaultReadablePort(curdrp) + } + + val drp = children[XOutput] find { _.primary } + drp + } + + override protected[xxml] def elaborateDependsConnections(inScopeSteps: Map[String,XStep]): Unit = { + for (name <- dependsOn.keySet) { + if (inScopeSteps.contains(name)) { + dependsOn.put(name, Some(inScopeSteps(name))) + } else { + error(XProcException.xsNotAStep(name, location)) + } + } + + val steps = mutable.HashMap.empty[String,XStep] ++ inScopeSteps + for (child <- children[XStep]) { + child match { + case _: XWhen => () + case _: XOtherwise => () + case _: XCatch => () + case _: XFinally => () + case _ => + if (child.name.isDefined) { + steps.put(child.name.get, child) + } + } + } + + for (child <- children[XStep]) { + child.elaborateDependsConnections(steps.toMap) + } + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + for (child <- allChildren) { + child.validate() + } + + val seenPorts = mutable.HashSet.empty[String] + val newChildren = ListBuffer.empty[XArtifact] + + for (input <- children[XInput]) { + if (seenPorts.contains(input.port)) { + input.error(XProcException.xsDupPortName(input.port, None)) + } else { + seenPorts += input.port + newChildren += input + } + } + + for (output <- children[XOutput]) { + if (seenPorts.contains(output.port)) { + output.error(XProcException.xsDupPortName(output.port, None)) + } else { + seenPorts += output.port + newChildren += output + } + } + + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XInput => () + case _: XOutput => () + case _: XWithInput => () + if (this.isInstanceOf[XIf]) { + newChildren += child + } else { + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + case _: XVariable => + newChildren += child + case _: XStep => + newChildren += child + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + allChildren = newChildren.toList + + if (children[XOutput].length == 1) { + val output = children[XOutput].head + if (!output.primarySpecified) { + output.primary = true + } + } + + constructDefaultOutput() + } + + protected def checkStepNameScoping(inScopeNames: Set[String]): Set[String] = { + val newScope = mutable.Set.empty[String] ++ inScopeNames + + if (name.isDefined) { + if (newScope.contains(name.get)) { + error(XProcException.xsDuplicateStepName(name.get, location)) + } + newScope += name.get + } + + for (step <- children[XStep]) { + if (step.name.isDefined) { + if (newScope.contains(step.name.get)) { + error(XProcException.xsDuplicateStepName(step.name.get, location)) + } + newScope += step.name.get + } + } + + for (step <- children[XContainer]) { + // newScope contains the names of all the children, but we need + // to exclude *this* child's name for each descent. + val childScope = mutable.Set.empty[String] ++ newScope + if (step.name.isDefined) { + childScope -= step.name.get + } + step.checkStepNameScoping(childScope.toSet) + } + + newScope.toSet + } + + def publicPipeConnections: Map[String,XPort] = { + val ports = mutable.HashMap.empty[String,XPort] + + for (input <- children[XInput]) { + val port = input.port + ports.put(s"${name.getOrElse(tumble_id)}/${port}", input) + } + + for (child <- children[XStep]) { + val name = child.stepName + for (output <- child.children[XOutput]) { + val port = output.port + ports.put(s"${name}/${port}", output) + } + for (output <- child.children[XWithOutput]) { + val port = output.port + ports.put(s"${name}/${port}", output) + } + } + + for (child <- children[XOption]) { + ports.put(s"${child.tumble_id}/result", child.children[XWithOutput].head) + } + for (child <- children[XVariable]) { + ports.put(s"${child.tumble_id}/result", child.children[XWithOutput].head) + } + + ports.toMap + } + + def privatePipeConnections: Map[String,XPort] = { + // Private pipe connections are ones that must be available + // to the pipeline engine, but aren't exposed to the pipeline + // author. For example, the pipeline author can't connect to the + // output ports on a p:when, but the p:choose step must be able to! + Map() + } + + override protected[xxml] def elaborateValidatePortConnections(ports: XPortBindingContext): Unit = { + super.elaborateValidatePortConnections(ports) + if (exceptions.isEmpty) { + this match { + case _: XLibrary => + () + case decl: XDeclareStep => + if (!decl.atomic) { + checkUnboundOutputs() + } + case _ => + checkUnboundOutputs() + } + } + } + + private def checkUnboundOutputs(): Unit = { + for (step <- children[XStep]) { + for (io <- step.children[XPort]) { + io match { + case _: XInput => + () + case _: XWithInput => + () + case port: XPort => + () + } + } + } + } + + override protected[xxml] def elaborateInsertContentTypeFilters(): Unit = { + for (child <- children[XInput]) { + if (!MediaType.OCTET_STREAM.allowed(child.contentTypes)) { + val filterable = true // FIXME: not necessary for compound steps where we can see what the input is + if (filterable) { + addInputFilter(child, new XContentTypeChecker(this, child)) + } + } + } + + for (child <- children[XStep]) { + child.elaborateInsertContentTypeFilters() + } + + for (child <- children[XOutput]) { + if (!MediaType.OCTET_STREAM.allowed(child.contentTypes)) { + var filter = false + for (pipe <- child.children[XPipe]) { + for (ctype <- pipe.from.get.contentTypes filter { _.inclusive }) { + filter = filter || !ctype.allowed(child.contentTypes) + } + } + if (filter) { + addOutputFilter(child, new XContentTypeChecker(this, child)) + } + } + } + } + + override protected def addInputFilter(child: XPort, filter: XStep): Unit = { + val firstChild = children[XStep].head + val xwi = new XWithInput(filter, "source") + xwi.primary = true + xwi.sequence = child.sequence + xwi.contentTypes = MediaType.MATCH_ANY.toList + xwi.addChild(new XPipe(xwi, child)) + val xwo = new XWithOutput(filter, "result") + xwo.primary = true + xwo.sequence = child.sequence + xwo.contentTypes = child.contentTypes + filter.addChild(xwi) + filter.addChild(xwo) + patchBinding(this, child, xwo) + insertBefore(filter, firstChild) + xwi.validate() + xwo.validate() + } + + private def addOutputFilter(child: XPort, filter: XStep): Unit = { + val xwi = new XWithInput(filter, "source") + xwi.primary = true + xwi.sequence = child.sequence + xwi.contentTypes = MediaType.MATCH_ANY.toList + xwi.allChildren = child.allChildren + child.allChildren = List() + + val xwo = new XWithOutput(filter, "result") + xwo.primary = true + xwo.sequence = child.sequence + xwo.contentTypes = child.contentTypes + filter.addChild(xwi) + filter.addChild(xwo) + child.addChild(new XPipe(child, filter.stepName, "result")) + addChild(filter) + xwi.validate() + xwo.validate() + } + + private def patchBinding(artifact: XArtifact, from: XPort, originPort: XPort): Unit = { + // Don't replace them while we're walking over the data structure + val replace = ListBuffer.empty[XPipe] + for (child <- artifact.allChildren) { + child match { + case pipe: XPipe => + if (pipe.from.isDefined && pipe.from.get == from) { + replace += pipe + } + case _ => + patchBinding(child, from, originPort) + } + } + for (pipe <- replace) { + val newPipe = new XPipe(pipe.parent.get, originPort) + pipe.parent.get.replaceChild(pipe, newPipe) + } + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("name", Some(stepName)) + dumpTree(sb, nodeName.toString, attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XContentTypeChecker.scala b/src/main/scala/com/xmlcalabash/model/xxml/XContentTypeChecker.scala new file mode 100644 index 0000000..6e56931 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XContentTypeChecker.scala @@ -0,0 +1,30 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.params.ContentTypeCheckerParams +import com.xmlcalabash.runtime.{StepProxy, XMLCalabashRuntime} + +class XContentTypeChecker(parentStep: XArtifact, xport: XPort) extends XAtomicStep(parentStep.config, XProcConstants.cx_content_type_checker) { + staticContext = parentStep.staticContext + _synthetic = true + parent = parentStep + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val inputPort = xport match { + case _: XInput => true + case _: XWithInput => true + case _ => false + } + + val params = new ContentTypeCheckerParams(xport.port, xport.contentTypes, parentStep.staticContext, + xport.select, inputPort, xport.sequence) + + val start = parent.asInstanceOf[ContainerStart] + val impl = stepImplementation + impl.configure(config, stepType, name, Some(params)) + + val proxy = new StepProxy(runtime, stepType, impl, staticContext) + runtime.addNode(this, start.addAtomic(proxy, "cx:content-type-checker")) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XDataSource.scala b/src/main/scala/com/xmlcalabash/model/xxml/XDataSource.scala new file mode 100644 index 0000000..3161eb4 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XDataSource.scala @@ -0,0 +1,77 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.util.MediaType +import net.sf.saxon.s9api.QName + +import scala.collection.mutable.ListBuffer + +abstract class XDataSource(config: XMLCalabash) extends XArtifact(config) { + protected var _drp: Option[XPort] = None + + protected[xxml] def drp: Option[XPort] = _drp + + protected def parseContentType(): Option[MediaType] = { + try { + val contentType = staticContext.parseSingleContentType(attr(XProcConstants._content_type)) + if (contentType.isDefined) { + contentType.get.assertValid + return contentType + } + } catch { + case ex: XProcException => + error(ex) + } + + None + } + + protected def resolveBindings(contextDependent: Boolean, refs: Set[QName], context: XNameBindingContext): Unit = { + if (!contextDependent) { + _drp = None + } + + val namepipe = ListBuffer.empty[XNameBinding] + for (ref <- refs) { + val cbind = context.inScopeConstants.get(ref) + val dbind = context.inScopeDynamics.get(ref) + if (cbind.isDefined) { + // ok + } else if (dbind.isDefined) { + dbind.get match { + case v: XVariable => + namepipe += v + case o: XOption => + namepipe += o + case _ => + error(XProcException.xiThisCantHappen(s"Unexpected name binding: ${dbind.get}")) + } + } else { + error(XProcException.xsNoBindingInExpression(ref, None)) + } + } + + if (namepipe.nonEmpty) { + val xwi = new XWithInput(this, "#bindings", false, true, MediaType.MATCH_ANY) + for (binding <- namepipe) { + val xstep = context.inScopeDynamics.get(binding.name) + val pipe = new XPipe(xwi, xstep.get.tumble_id, "result") + xwi.addChild(pipe) + } + addChild(xwi) + } + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + super.elaborateDefaultReadablePort(initial) + } + + override protected[xxml] def elaboratePortConnections(): Unit = { + if (drp.isDefined && children[XDataSource].isEmpty) { + addChild(new XPipe(drp.get)) + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XDeclContainer.scala b/src/main/scala/com/xmlcalabash/model/xxml/XDeclContainer.scala new file mode 100644 index 0000000..0ca3f5d --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XDeclContainer.scala @@ -0,0 +1,226 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.config.StepSignature +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import net.sf.saxon.s9api.QName + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +abstract class XDeclContainer(config: XMLCalabash) extends XContainer(config) { + protected var _psvi_required = Option.empty[Boolean] + protected var _xpath_version = Option.empty[Double] + protected var _version = Option.empty[Double] + protected var _skeletons: mutable.HashMap[QName, XSkeletonStepSignature] = mutable.HashMap.empty[QName, XSkeletonStepSignature] + protected var _builtinLibraries: ListBuffer[XLibrary] = ListBuffer.empty[XLibrary] + protected val _precedingStaticOptions: ListBuffer[XOption] = ListBuffer.empty[XOption] + protected val _inScopeSteps: mutable.HashSet[XDeclareStep] = mutable.HashSet.empty[XDeclareStep] + protected val _xoptions: mutable.HashMap[QName,XOption] = mutable.HashMap.empty[QName, XOption] + + + def inScopeSteps: List[XDeclareStep] = _inScopeSteps.toList + protected[xxml] def addInScopeSteps(steps: List[XDeclareStep]): Unit = { + for (step <- steps) { + if (step.visibility == "public") { + _inScopeSteps += step + } else { + println("bang") + } + } + } + + def builtinLibraries: List[XLibrary] = _builtinLibraries.toList + protected[xxml] def builtinLibraries_=(libs: List[XLibrary]): Unit = { + _builtinLibraries.clear() + _builtinLibraries ++= libs + } + + def findDeclaration(stepType: QName): Option[XDeclareStep] = { + for (step <- inScopeSteps) { + if (step.stepType.isDefined && step.stepType.get == stepType) { + return Some(step) + } + } + + for (lib <- builtinLibraries) { + val decl = lib.findDeclaration(stepType) + if (decl.isDefined) { + return decl + } + } + + None + } + + protected[xxml] def xelaborate(): Unit = { + val stepList = ListBuffer.empty[XDeclareStep] + + val seenOptions = ListBuffer.empty[XOption] + val newChildren = ListBuffer.empty[XArtifact] + for (child <- allChildren) { + child match { + case opt: XOption => + // Peek to see if this is a static option; it hasn't been validated + // yet, so this will be a bit speculative... + val static = opt.attributes.get(XProcConstants._static) + if (static.isDefined && static.get == "true") { + seenOptions += opt + } + newChildren += child + case decl: XDeclareStep => + decl._precedingStaticOptions ++= _precedingStaticOptions + decl._precedingStaticOptions ++= seenOptions.toList + stepList += decl + case _ => + newChildren += child + } + } + + allChildren = newChildren.toList + + val stepTypes = mutable.HashSet.empty[QName] + for (step <- _inScopeSteps) { + if (step.stepType.isDefined) { + stepTypes += step.stepType.get + } + } + + this match { + case step: XDeclareStep => + step.elaborateStepApi() + step._inScopeSteps += step + step._inScopeSteps ++= stepList + case lib: XLibrary => + lib._inScopeSteps ++= stepList.toList + lib._builtinLibraries = _builtinLibraries + lib.elaborateLibraryApi() + case _ => + throw XProcException.xiThisCantHappen("Step declaration container is neither declare-step nor library?") + } + + for (step <- stepList) { + step._inScopeSteps ++= _inScopeSteps + step._builtinLibraries = _builtinLibraries + step.elaborateStepApi() + + if (step.stepType.isDefined) { + if (stepTypes.contains(step.stepType.get)) { + throw XProcException.xsDupStepType(step.stepType.get, location) + } + } + } + + validate() + + for (step <- stepList) { + step.xelaborate() + } + } + + protected[xxml] def errors: List[Exception] = { + this match { + case decl: XDeclareStep => + errors(List(decl)) + case _ => + exceptions ++ errors(inScopeSteps) + } + } + + private def errors(steps: List[XDeclareStep]): List[Exception] = { + val exlist = ListBuffer.empty[Exception] + for (step <- steps) { + exlist ++= step.exceptions + //exlist ++= errors(step.inScopeSteps) + } + exlist.toList + } + + protected[xxml] def skeletons: List[XSkeletonStepSignature] = _skeletons.values.toList + + protected[xxml] def findSkeleton(stepType: QName): Option[XSkeletonStepSignature] = { + if (_skeletons.contains(stepType)) { + _skeletons.get(stepType) + } else { + if (parent.isDefined) { + declarationContainer.findSkeleton(stepType) + } else { + for (lib <- builtinLibraries) { + if (lib.findSkeleton(stepType).isDefined) { + return lib.findSkeleton(stepType) + } + } + None + } + } + } + + protected[xxml] def addSkeleton(skeleton: XSkeletonStepSignature): Unit = { + _skeletons.put(skeleton.stepType, skeleton) + } + + def inputPorts: Set[String] = { + val ports = mutable.HashSet.empty[String] + for (child <- children[XInput]) { + ports += child.port + } + ports.toSet + } + + def input(port: String): XInput = { + for (child <- children[XInput]) { + if (child.port == port) { + return child + } + } + throw XProcException.xiThisCantHappen(s"Attempt to get input '${port}' on decl that doesn't have one") + } + + def outputPorts: Set[String] = { + val ports = mutable.HashSet.empty[String] + for (child <- children[XOutput]) { + ports += child.port + } + ports.toSet + } + + def output(port: String): XOutput = { + for (child <- children[XOutput]) { + if (child.port == port) { + return child + } + } + throw XProcException.xiThisCantHappen(s"Attempt to get output '${port}' on decl that doesn't have one") + } + + def options: List[XNameBinding] = { + children[XWithOption] ++ children[XOption] + } + + def optionNames: Set[QName] = { + val names = mutable.HashSet.empty[QName] + children[XWithOption] foreach { names += _.name } + children[XOption] foreach { names += _.name } + names.toSet + } + + def option(name: QName): Option[XOption] = _xoptions.get(name) + + protected[xxml] def extractSteps(): Unit = { + val newChildren = ListBuffer.empty[XArtifact] + for (child <- allChildren) { + child match { + case decl: XDeclareStep => + decl.extractSteps() + _inScopeSteps += decl + case step: XStep => + newChildren += step + case _ => + newChildren += child + } + } + + allChildren = newChildren.toList + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XDeclareStep.scala b/src/main/scala/com/xmlcalabash/model/xxml/XDeclareStep.scala new file mode 100644 index 0000000..1422832 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XDeclareStep.scala @@ -0,0 +1,408 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.exceptions.JafplLoopDetected +import com.jafpl.steps.{Manifold, PortCardinality, PortSpecification} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.runtime.XMLCalabashRuntime +import com.xmlcalabash.util.XProcVarValue +import net.sf.saxon.s9api.QName + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XDeclareStep(config: XMLCalabash) extends XDeclContainer(config) { + private var _visibility = Option.empty[String] + private var _implclass = Option.empty[String] + + private var _computedApi = false + private val _xinputs = mutable.HashMap.empty[String, XInput] + private val _xoutputs = mutable.HashMap.empty[String, XOutput] + + def visibility: String = _visibility.getOrElse("public") + def atomic: Boolean = children[XStep].isEmpty + def stepType: Option[QName] = _type + def implementationClass: Option[String] = _implclass + + // ======================================================================================= + + def runtime(): XMLCalabashRuntime = { + val runtime = new XMLCalabashRuntime(this) + val pipeline = runtime.graph.addPipeline(stepName, manifold) + + for (port <- inputPorts) { + runtime.graph.addInput(pipeline, port) + } + + for (port <- outputPorts) { + runtime.graph.addOutput(pipeline, port) + } + + runtime.addNode(this, pipeline) + + graphNodes(runtime, pipeline) + graphEdges(runtime) + + try { + runtime.init(this) + } catch { + case _: JafplLoopDetected => + throw XProcException.xsLoop("???", "???", location) + } + + runtime + } + + private def manifold: Manifold = { + val inputMap = mutable.HashMap.empty[String,PortCardinality] + for (input <- children[XInput]) { + if (input.sequence) { + inputMap.put(input.port, PortCardinality.ZERO_OR_MORE) + } else { + if (input.defaultInputs.nonEmpty) { + // Allow it to be empty, the default will provide something + inputMap.put(input.port, new PortCardinality(0, 1)) + } else { + inputMap.put(input.port, PortCardinality.EXACTLY_ONE) + } + } + } + val outputMap = mutable.HashMap.empty[String,PortCardinality] + for (output <- outputs) { + if (output.sequence) { + outputMap.put(output.port, PortCardinality.ZERO_OR_MORE) + } else { + outputMap.put(output.port, PortCardinality.EXACTLY_ONE) + } + } + new Manifold(new PortSpecification(inputMap.toMap), new PortSpecification(outputMap.toMap)) + } + + def patchOptions(bindings: Map[QName,XProcVarValue]): Unit = { + for (child <- children[XOption]) { + child.runtimeBindings(bindings) + } + } + + // ======================================================================================= + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + try { + _type = staticContext.parseQName(attr(XProcConstants._type)) + if (_type.isDefined) { + if (_type.get.getNamespaceURI == "") { + throw XProcException.xsBadStepTypeNamespace(location) + } + if (!config.standardLibraryParser && _type.get.getNamespaceURI == XProcConstants.ns_p) { + throw XProcException.xsBadStepTypeNamespace(_type.get.getNamespaceURI, location) + } + } + + _psvi_required = staticContext.parseBoolean(attr(XProcConstants._psvi_required)) + + if (attributes.contains(XProcConstants._xpath_version)) { + val vstr = attr(XProcConstants._xpath_version).get + _xpath_version = Some(vstr.toDouble) + } + + if (attributes.contains(XProcConstants._version)) { + val vstr = attr(XProcConstants._version).get + try { + _version = Some(vstr.toDouble) + if (_version.get != 3.0) { + error(XProcException.xsInvalidVersion(_version.get, None)) + } + } catch { + case _: NumberFormatException => + error(XProcException.xsBadVersion(vstr, None)) + } + } + if (_version.isEmpty && (parent.isEmpty || parent.get.synthetic)) { + error(XProcException.xsVersionRequired(None)) + } + + _visibility = attr(XProcConstants._visibility) + if (_visibility.isDefined) { + if (_visibility.get != "public" && _visibility.get != "private") { + error(XProcException.xdBadVisibility(_visibility.get, None)) + } + if (parent.isDefined && parent.get.synthetic) { + // If our parent is synthetic, then this was a p:declare-step imported directly + // (and the library container has just been synthesized for consistency). + // Directly imported p:declare-steps are always visible. + _visibility = Some("public") + } + } + } catch { + case ex: Exception => + error(ex) + } + } + + // ======================================================================================= + // ======================================================================================= + // ======================================================================================= + // ======================================================================================= + + /** + * Sort out the inputs, outputs, and options of the step, ignoring its body. + * This way, when we do look at the body of each step, we'll already have sorted + * out the API of any steps it refers to. + */ + protected[xxml] def elaborateStepApi(): Unit = { + if (_computedApi) { + return + } + _computedApi = true + + checkAttributes() + checkEmptyAttributes() + + if (errors.nonEmpty) return + + xcomputeSignature() + } + + private def xcomputeSignature(): Unit = { + var primaryInput = Option.empty[XInput] + var primaryOutput = Option.empty[XOutput] + val staticOptions = ListBuffer.empty[XNameBinding] + + staticOptions ++= _precedingStaticOptions.toList + staticContext = staticContext.withConstants(staticOptions.toList) + + // N.B. You can't re-order the children + for (child <- allChildren) { + child match { + case input: XInput => + input.checkAttributes() + input.checkEmptyAttributes() + + input.staticContext = input.staticContext.withConstants(staticOptions.toList) + + input.checkDefaultInputs() + + if (_xinputs.contains(input.port)) { + input.error(XProcException.xsDupPortName(input.port, None)) + } else { + _xinputs.put(input.port, input) + if (input.primary) { + if (primaryInput.isDefined) { + input.error(XProcException.xsDupPrimaryInputPort(input.port, primaryInput.get.port, None)) + } else { + primaryInput = Some(input) + } + } + } + case output: XOutput => + output.checkAttributes() + output.checkEmptyAttributes() + + output.staticContext = output.staticContext.withConstants(staticOptions.toList) + + if (_xoutputs.contains(output.port)) { + output.error(XProcException.xsDupPortName(output.port, None)) + } else { + _xoutputs.put(output.port, output) + if (output.primary) { + if (primaryOutput.isDefined) { + output.error(XProcException.xsDupPrimaryOutputPort(output.port, primaryOutput.get.port, None)) + } else { + primaryOutput = Some(output) + } + } + } + case option: XOption => + option.checkAttributes() + option.checkEmptyAttributes() + if (_xoptions.contains(option.name)) { + error(XProcException.xsDuplicateOptionName(option.name, None)) + } else { + _xoptions.put(option.name, option) + } + if (option.static) { + staticOptions += option + } + + case _ => + () + } + } + + if (primaryInput.isEmpty && inputPorts.size == 1) { + val input = children[XInput].head + if (!input.primarySpecified) { + input.primary = true + } + } + + if (primaryOutput.isEmpty && outputPorts.size == 1) { + val output = children[XOutput].head + if (!output.primarySpecified) { + output.primary = true + } + } + } + + override protected[xxml] def validate(): Unit = { + val seenPorts = mutable.HashSet.empty[String] + val newChildren = ListBuffer.empty[XArtifact] + + // N.B. You can't re-order the children + for (child <- allChildren) { + child.validate() + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + // N.B. input, output, and option were validated in elaborateStepApi() + case input: XInput => + if (seenPorts.contains(input.port)) { + input.error(XProcException.xsDupPortName(input.port, None)) + } else { + seenPorts += input.port + } + newChildren += input + case output: XOutput => + if (seenPorts.contains(output.port)) { + output.error(XProcException.xsDupPortName(output.port, None)) + } else { + seenPorts += output.port + } + newChildren += output + case option: XOption => + newChildren += option + case _: XImport => + () + case imp: XImportFunctions => + newChildren += imp + case v: XVariable => + newChildren += v + case step: XStep => + newChildren += step + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + allChildren = newChildren.toList + + checkStepNameScoping(Set()) + + if (exceptions.nonEmpty) { + return + } + + if (atomic) { + if (stepType.isDefined) { + _implclass = config.externalSteps.get(stepType.get) + } + return + } + + if (children[XOutput].length == 1) { + val output = children[XOutput].head + if (!output.primarySpecified) { + output.primary = true + } + } + + constructDefaultOutput() + + if (exceptions.isEmpty) { + elaborateSyntacticSugar() + } + + if (exceptions.isEmpty) { + elaborateDefaultReadablePort(None) + } + + if (exceptions.isEmpty) { + var context = new XNameBindingContext() + for (static <- _precedingStaticOptions) { + context = context.withBinding(static) + } + elaborateNameBindings(context) + } + + if (exceptions.isEmpty) { + hoistSourcesToPipes() + } + + if (exceptions.isEmpty) { + elaboratePortConnections() + if (exceptions.isEmpty) { + hoistSourcesToPipes() + } + } + + if (exceptions.isEmpty) { + elaborateValidatePortConnections(new XPortBindingContext()) + if (exceptions.isEmpty) { + hoistSourcesToPipes() + if (exceptions.isEmpty) { + elaborateValidatePortConnections(new XPortBindingContext()) + } + } + } + + if (exceptions.isEmpty) { + elaborateDependsConnections(Map()) + } + + if (exceptions.isEmpty) { + elaborateDynamicVariables() + } + + if (exceptions.isEmpty) { + elaborateDynamicOptions() + } + + if (exceptions.isEmpty) { + elaborateInsertSelectFilters() + if (exceptions.isEmpty) { + elaborateValidatePortConnections(new XPortBindingContext()) + } + } + + if (exceptions.isEmpty) { + elaborateInsertContentTypeFilters() + if (exceptions.isEmpty) { + elaborateValidatePortConnections(new XPortBindingContext()) + } + } + + if (exceptions.isEmpty) { + computeReadsFrom() + } + } + + // ======================================================================================= + // ======================================================================================= + // ======================================================================================= + // ======================================================================================= + + override protected[xxml] def elaborateInsertContentTypeFilters(): Unit = { + if (!atomic) { + super.elaborateInsertContentTypeFilters() + } + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + if (_type.isDefined) { + attr.put("type", Some(_type.get.getEQName)) + } + attr.put("name", Some(stepName)) + dumpTree(sb, "p:declare-step", attr.toMap) + } + + override def toString: String = { + if (_type.isDefined) { + s"${_type.get}: ${stepName}" + } else { + stepName + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XDocument.scala b/src/main/scala/com/xmlcalabash/model/xxml/XDocument.scala new file mode 100644 index 0000000..41a29f7 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XDocument.scala @@ -0,0 +1,145 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.messages.Message +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.config.DocumentRequest +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants.{ValueTemplate, _content_type} +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants, XValueParser} +import com.xmlcalabash.runtime.params.DocumentLoaderParams +import com.xmlcalabash.runtime.{StaticContext, XProcVtExpression} +import com.xmlcalabash.util.{MediaType, Urify} +import net.sf.saxon.s9api.QName +import net.sf.saxon.sapling.Saplings.doc + +import java.net.URI +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XDocument(config: XMLCalabash) extends XDataSource(config) { + private var _href: String = _ + private var _hrefAvt: ValueTemplate = _ + private var _contentType = Option.empty[MediaType] + private var _documentProperties = Option.empty[String] + private var _parameters = Option.empty[String] + private var _contextDependent = false + + def contextDependent: Boolean = _contextDependent + + protected[xxml] def this(parent: XArtifact, href: String) = { + this(parent.config) + this.parent = parent + this.staticContext = parent.staticContext + _synthetic = true + _href = href + _hrefAvt = XValueParser.parseAvt(_href) + } + + protected[xxml] def this(parent: XArtifact, copy: XDocument) = { + this(copy.config) + _href = copy._href + _hrefAvt = copy._hrefAvt + _contentType = copy._contentType + _documentProperties = copy._documentProperties + _parameters = copy._parameters + _contextDependent = copy._contextDependent + _drp = copy._drp + staticContext = copy.staticContext + this.parent = parent + } + + def loaderParams: DocumentLoaderParams = { + // FIXME: context_provided is always false + new DocumentLoaderParams(_hrefAvt, _contentType, _parameters, _documentProperties, _contextDependent, staticContext) + } + + def loadDocument(): DocumentRequest = { + val expr = new XProcVtExpression(staticContext, _hrefAvt, true) + val msg = config.expressionEvaluator.value(expr, List(), staticContext.inscopeConstantBindings, None) + val href = if (staticContext.baseURI.isDefined) { + staticContext.baseURI.get.resolve(msg.item.toString) + } else { + new URI(msg.item.toString) + } + new DocumentRequest(href, _contentType, location) + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + if (attributes.contains(XProcConstants._href)) { + _href = attr(XProcConstants._href).get + } else { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._href, None)) + } + _contentType = parseContentType() + _documentProperties = attr(XProcConstants._document_properties) + _parameters = attr(XProcConstants._parameters) + _hrefAvt = XValueParser.parseAvt(_href) + } + + override protected[xxml] def validate(): Unit = { + if (!synthetic) { + checkAttributes() + checkEmptyAttributes() + } + + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + allChildren = List() + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + super.elaborateDefaultReadablePort(initial) + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + try { + val refs = mutable.HashSet.empty[QName] + + val parser = new XValueParser(config, staticContext, _hrefAvt) + refs ++= parser.variables + _contextDependent = _contextDependent || parser.contextDependent + + if (_documentProperties.isDefined) { + val parser = new XValueParser(config, staticContext, _documentProperties.get) + refs ++= parser.variables + _contextDependent = _contextDependent || parser.contextDependent + } + + if (_parameters.isDefined) { + val parser = new XValueParser(config, staticContext, _parameters.get) + refs ++= parser.variables + _contextDependent = _contextDependent || parser.contextDependent + } + + resolveBindings(_contextDependent, refs.toSet, initial) + } catch { + case ex: Exception => + error(ex) + } + + staticContext = staticContext.withConstants(initial) + + initial + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("href", Option(_href)) + + if (drp.isDefined) { + attr.put("drp", Some(drp.get.tumble_id)) + } else { + attr.put("drp", Some("!undefined")) + } + + dumpTree(sb, "p:document", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XDocumentLoader.scala b/src/main/scala/com/xmlcalabash/model/xxml/XDocumentLoader.scala new file mode 100644 index 0000000..100f948 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XDocumentLoader.scala @@ -0,0 +1,33 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.{StepProxy, XMLCalabashRuntime} + +class XDocumentLoader(doc: XDocument, parentStep: XArtifact) extends XAtomicStep(doc.config, if (doc.contextDependent) { XProcConstants.cx_document_loader_vt } else { XProcConstants.cx_document_loader }) { + staticContext = doc.staticContext + _synthetic = true + parent = parentStep + + allChildren = doc.allChildren + if (doc.contextDependent) { + val xwi = new XWithInput(this, "source") + xwi.drp = doc.drp + doc.allChildren = List() + addChild(xwi) + } + + val output = new XWithOutput(this, "result") + output.validate() + addChild(output) + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[ContainerStart] + val impl = stepImplementation + val params = Some(doc.loaderParams) + impl.configure(config, stepType, name, params) + + val proxy = new StepProxy(runtime, stepType, impl, staticContext) + runtime.addNode(this, start.addAtomic(proxy, stepType.toString)) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XDocumentation.scala b/src/main/scala/com/xmlcalabash/model/xxml/XDocumentation.scala new file mode 100644 index 0000000..cb5425a --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XDocumentation.scala @@ -0,0 +1,15 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.model.util.SaxonTreeBuilder +import net.sf.saxon.s9api.XdmNode + +class XDocumentation(config: XMLCalabash, val content: XdmNode) extends XArtifact(config) { + override protected[xxml] def validate(): Unit = { + // nop + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + // supppress + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XEmpty.scala b/src/main/scala/com/xmlcalabash/model/xxml/XEmpty.scala new file mode 100644 index 0000000..013c38b --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XEmpty.scala @@ -0,0 +1,34 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.SaxonTreeBuilder + +class XEmpty(config: XMLCalabash) extends XDataSource(config) { + def this(parent: XArtifact) = { + this(parent.config) + staticContext = parent.staticContext + this.parent = parent + _synthetic = true + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + for (child <- allChildren) { + child.validate() + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + allChildren = List() + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + dumpTree(sb, "p:empty", Map()) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XEmptyLoader.scala b/src/main/scala/com/xmlcalabash/model/xxml/XEmptyLoader.scala new file mode 100644 index 0000000..021ada2 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XEmptyLoader.scala @@ -0,0 +1,26 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.{StepProxy, XMLCalabashRuntime} +import com.xmlcalabash.runtime.params.EmptyLoaderParams + +class XEmptyLoader(parentStep: XArtifact) extends XAtomicStep(parentStep.config, XProcConstants.cx_empty_loader) { + staticContext = parentStep.staticContext + _synthetic = true + parent = parentStep + + val output = new XWithOutput(this, "result") + output.validate() + addChild(output) + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val params = new EmptyLoaderParams(staticContext) + val start = parent.asInstanceOf[ContainerStart] + val impl = stepImplementation + impl.configure(config, stepType, name, Some(params)) + + val proxy = new StepProxy(runtime, stepType, impl, staticContext) + runtime.addNode(this, start.addAtomic(proxy, "p:empty")) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XFinally.scala b/src/main/scala/com/xmlcalabash/model/xxml/XFinally.scala new file mode 100644 index 0000000..749a400 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XFinally.scala @@ -0,0 +1,26 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{Node, TryCatchStart} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.runtime.XMLCalabashRuntime + +class XFinally(config: XMLCalabash) extends XTryCatchBranch(config) { + + override protected[xxml] def validate(): Unit = { + super.validate() + + val primary = children[XOutput] find { _.primary == true } + if (primary.isDefined) { + error(XProcException.xsPrimaryOutputOnFinally(primary.get.port, location)) + } + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[TryCatchStart] + val node = start.addFinally(stepName, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } + +} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XForEach.scala b/src/main/scala/com/xmlcalabash/model/xxml/XForEach.scala new file mode 100644 index 0000000..bdd7d29 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XForEach.scala @@ -0,0 +1,15 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.runtime.XMLCalabashRuntime + +class XForEach(config: XMLCalabash) extends XLoopingStep(config) { + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[ContainerStart] + val node = start.addForEach(stepName, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XForLoop.scala b/src/main/scala/com/xmlcalabash/model/xxml/XForLoop.scala new file mode 100644 index 0000000..fc845e9 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XForLoop.scala @@ -0,0 +1,50 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.runtime.XMLCalabashRuntime +import net.sf.saxon.s9api.QName + +class XForLoop(config: XMLCalabash) extends XLoopingStep(config) { + private val _from = new QName("from") + private val _to = new QName("to") + private val _by = new QName("by") + private var countFrom = 1L + private var countTo = 0L + private var countBy = 1L + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + + if (attributes.contains(_from)) { + countFrom = attr(_from).get.toLong + } + + if (attributes.contains(_to)) { + countTo = attr(_to).get.toLong + } else { + throw new RuntimeException("to is required") + } + + if (attributes.contains(_by)) { + countBy = attr(_by).get.toLong + } + + if (countBy == 0) { + throw XProcException.xiAttempToCountByZero(location) + } + + if ((countFrom > countTo && countBy > 0) + || (countFrom < countTo && countBy < 0)) { + logger.debug(s"Counting from $countFrom to $countTo by $countBy will never execute") + } + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[ContainerStart] + val node = start.addFor(stepName, countFrom, countTo, countBy, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XForUntil.scala b/src/main/scala/com/xmlcalabash/model/xxml/XForUntil.scala new file mode 100644 index 0000000..f2b4188 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XForUntil.scala @@ -0,0 +1,74 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.runtime.XMLCalabashRuntime +import com.xmlcalabash.util.{MediaType, XmlItemComparator} +import net.sf.saxon.s9api.QName + +class XForUntil(config: XMLCalabash) extends XLoopingStep(config) { + private val _max_iterations = new QName("max-iterations") + private val _comparator = new QName("comparator") + private val _return = new QName("return") + private var maxIterations: Long = -1 + private var comparator: String = "deep-equal($a,$b)" + private var returnSet: String = "last" + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + + if (attributes.contains(_max_iterations)) { + maxIterations = attr(_max_iterations).get.toInt + } + + if (attributes.contains(_comparator)) { + comparator = attr(_comparator).get + } + + if (attributes.contains(_return)) { + returnSet = attr(_return).get + if (returnSet != "last" && returnSet != "all") { + throw new RuntimeException("return must be last or all") + } + } + } + + override protected[xxml] def validate(): Unit = { + super.validate() + + var testOutput = Option.empty[XOutput] + for (child <- children[XOutput]) { + if (child.port == "test") { + testOutput = Some(child) + } + } + + if (testOutput.isEmpty) { + val output = new XOutput(this, Some("test")) + output.primary = false + output.sequence = false + output.contentTypes = MediaType.MATCH_ANY + + val firstStep = children[XStep].head + val step = children[XStep].last + val pout = step.primaryOutput + if (pout.isDefined) { + val pipe = new XPipe(step.primaryOutput.get) + output.addChild(pipe) + if (Option(firstStep).isDefined) { + insertBefore(output, firstStep) + } else { + addChild(output) + } + } + } + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val compare = new XmlItemComparator(config, comparator, maxIterations, this) + val start = parent.asInstanceOf[ContainerStart] + val node = start.addUntil(compare, returnSet=="all", stepName, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XForWhile.scala b/src/main/scala/com/xmlcalabash/model/xxml/XForWhile.scala new file mode 100644 index 0000000..9bdff9b --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XForWhile.scala @@ -0,0 +1,45 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.XMLCalabashRuntime +import com.xmlcalabash.util.XmlItemTester +import net.sf.saxon.s9api.QName + +class XForWhile(config: XMLCalabash) extends XLoopingStep(config) { + private val _max_iterations = new QName("max-iterations") + private val _return = new QName("return") + private var maxIterations: Long = -1 + private var test: String = "" + private var returnSet: String = "last" + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + + if (attributes.contains(_max_iterations)) { + maxIterations = attr(_max_iterations).get.toInt + } + + if (attributes.contains(XProcConstants._test)) { + test = attr(XProcConstants._test).get + } else { + throw new RuntimeException("test is required") + } + + if (attributes.contains(_return)) { + returnSet = attr(_return).get + if (returnSet != "last" && returnSet != "all") { + throw new RuntimeException("return must be last or all") + } + } + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val tester = new XmlItemTester(runtime, test, maxIterations, this) + val start = parent.asInstanceOf[ContainerStart] + val node = start.addWhile(tester, returnSet == "all", stepName, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XGraphableArtifact.scala b/src/main/scala/com/xmlcalabash/model/xxml/XGraphableArtifact.scala new file mode 100644 index 0000000..1b02a3c --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XGraphableArtifact.scala @@ -0,0 +1,8 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.Node +import com.xmlcalabash.runtime.XMLCalabashRuntime + +trait XGraphableArtifact { + def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XGroup.scala b/src/main/scala/com/xmlcalabash/model/xxml/XGroup.scala new file mode 100644 index 0000000..e24cbf7 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XGroup.scala @@ -0,0 +1,28 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node, TryCatchStart} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.XMLCalabashRuntime + +class XGroup(config: XMLCalabash) extends XContainer(config) { + def this(parent: XContainer) = { + this(parent.config) + this.parent = parent + staticContext = parent.staticContext + _synthetic = true + _syntheticName = Some(XProcConstants.p_group) + } + + override def graphNodes(runtime: XMLCalabashRuntime, parNode: Node): Unit = { + val node = if (parent.get.isInstanceOf[XTry]) { + val start = parNode.asInstanceOf[TryCatchStart] + start.addTry(stepName, containerManifold) + } else { + val start = parNode.asInstanceOf[ContainerStart] + start.addGroup(stepName, containerManifold) + } + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XIf.scala b/src/main/scala/com/xmlcalabash/model/xxml/XIf.scala new file mode 100644 index 0000000..0513bd6 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XIf.scala @@ -0,0 +1,61 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants + +class XIf(config: XMLCalabash) extends XChooseBranch(config) { + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + val coll = attr(XProcConstants._collection) + if (coll.isDefined) { + coll.get match { + case "true" => _collection = true + case "false" => _collection = false + case _ => + error(XProcException.xsBadTypeValue(coll.get, "xs:boolean", None)) + } + } + + if (attributes.contains(XProcConstants._test)) { + _test = attr(XProcConstants._test).get + } else { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._test, None)) + } + } + + override protected[xxml] def validate(): Unit = { + super.validate() + val xwi = children[XWithInput].headOption + if (xwi.isDefined) { + if (xwi.get.port != "#anon") { + error(XProcException.xsPortNotAllowed(xwi.get.port, stepName, location)) + } + xwi.get.port = "condition" + } + + val primary = children[XOutput] find { _.primary } + if (primary.isEmpty) { + error(XProcException.xsPrimaryOutputRequired(location)) + } + } + + override def elaborateSyntacticSugar(): Unit = { + val choose = new XChoose(parent.get) + choose.dependsOn ++= dependsOn + choose.tumble_id = tumble_id + if (name.isDefined) { + choose.stepName = name.get + } + val when = new XWhen(choose, _test, _collection, true) + choose.addChild(when) + for (child <- allChildren) { + when.addChild(child) + } + allChildren = List() + parent.get.replaceChild(this, choose) + choose.elaborateSyntacticSugar() + choose.validate() + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XImport.scala b/src/main/scala/com/xmlcalabash/model/xxml/XImport.scala new file mode 100644 index 0000000..dcec797 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XImport.scala @@ -0,0 +1,56 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} + +import java.net.URI +import scala.collection.mutable + +class XImport(config: XMLCalabash, val library: XLibrary) extends XArtifact(config) { + private var _href: URI = _ + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + try { + if (attributes.contains(XProcConstants._href)) { + _href = staticContext.baseURI.get.resolve(attr(XProcConstants._href).get) + } else { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._href, None)) + } + } catch { + case ex: Exception => + error(ex) + } + } + + override protected[xxml] def validate(): Unit = { + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + allChildren = List() + + val cont = ancestor[XDeclContainer].get + for (step <- library.inScopeSteps) { + if (step.stepType.isDefined) { + for (istep <- cont.inScopeSteps) { + if (istep.stepType.isDefined && istep.stepType.get == step.stepType.get) { + throw XProcException.xsDupStepType(step.stepType.get, istep.location) + } + } + } + } + cont.addInScopeSteps(library.inScopeSteps) + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("href", Option(_href)) + dumpTree(sb, "p:import", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XImportFunctions.scala b/src/main/scala/com/xmlcalabash/model/xxml/XImportFunctions.scala new file mode 100644 index 0000000..303e8f0 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XImportFunctions.scala @@ -0,0 +1,44 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} + +import java.net.URI +import scala.collection.mutable + +class XImportFunctions(config: XMLCalabash) extends XArtifact(config) { + private var _href: URI = _ + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + try { + if (attributes.contains(XProcConstants._href)) { + _href = staticContext.baseURI.get.resolve(attr(XProcConstants._href).get) + } else { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._href, None)) + } + } catch { + case ex: Exception => + error(ex) + } + } + + protected[xxml] def elaborateDeclarations(): Unit = { + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + allChildren = List() + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("href", Option(_href)) + dumpTree(sb, "p:import", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XInline.scala b/src/main/scala/com/xmlcalabash/model/xxml/XInline.scala new file mode 100644 index 0000000..31599fa --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XInline.scala @@ -0,0 +1,272 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants, XValueParser} +import com.xmlcalabash.util.{MediaType, S9Api} +import net.sf.saxon.s9api.{Axis, QName, XdmNode, XdmNodeKind} + +import java.io.UnsupportedEncodingException +import java.util.Base64 +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.jdk.CollectionConverters.IteratorHasAsScala + +class XInline(config: XMLCalabash, val content: XdmNode, implied: Boolean) extends XDataSource(config) { + private var _contentType = Option.empty[MediaType] + private var _documentProperties = Option.empty[String] + private var _encoding = Option.empty[String] + private var _exclude_inline_prefixes = Option.empty[String] + private var _expandText = false + private val _excludeURIs = mutable.HashSet.empty[String] + private val _valueTemplates = ListBuffer.empty[String] + private var _contextDependent = false + _synthetic = implied + + if (Option(content).isEmpty || content.getNodeKind != XdmNodeKind.DOCUMENT) { + throw XProcException.xiThisCantHappen(s"Attempt to create p:inline from something that isn't a document node: ${content}") + } + + def this(config: XMLCalabash, parent: XArtifact, srcNode: XdmNode) = { + this(config, srcNode, true) + this.parent = parent + staticContext = parent.staticContext + } + + protected[xxml] def this(parent: XArtifact, copy: XInline) = { + this(copy.config, copy.content, copy.synthetic) + _contentType = copy._contentType + _documentProperties = copy._documentProperties + _encoding = copy._encoding + _exclude_inline_prefixes = copy._exclude_inline_prefixes + _expandText = copy._expandText + _excludeURIs ++= copy._excludeURIs + _valueTemplates ++= copy._valueTemplates + _contextDependent = copy._contextDependent + _drp = copy._drp + staticContext = copy.staticContext + this.parent = parent + } + + def contentType: Option[MediaType] = _contentType + + def encoding: Option[String] = _encoding + + def documentProperties: Option[String] = _documentProperties + + def expandText: Boolean = _expandText + + def excludeURIs: Set[String] = _excludeURIs.toSet + + def contextDependent: Boolean = _contextDependent + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + + val saveContentType = attributes.get(XProcConstants._content_type) + + _contentType = parseContentType() + _documentProperties = attr(XProcConstants._document_properties) + _encoding = attr(XProcConstants._encoding) + _exclude_inline_prefixes = attr(XProcConstants._exclude_inline_prefixes) + + if (_contentType.isDefined && _encoding.isEmpty) { + if (_contentType.get.charset.isDefined) { + error(XProcException.xdCharsetWithoutEncoding(saveContentType.get, None)) + } + } + + if (_encoding.isDefined) { + if (_encoding.get == "base64") { + if (_contentType.isDefined && _contentType.get.markupContentType) { + error(XProcException.xdCannotEncodeMarkup(_encoding.get, _contentType.get, None)) + } + + val charset = if (_contentType.isDefined) { + _contentType.get.charset.getOrElse("UTF-8") + } else { + "UTF-8" + } + + // Can I trust you? + try { + // Apparently the decoder won't acept newlines in the data... + val str = content.getStringValue.trim.replace("\n", "") + val bytes = Base64.getDecoder.decode(str) + if (contentType.isDefined && contentType.get.textContentType) { + new String(bytes, charset) + } + } catch { + case _: IllegalArgumentException => + error(XProcException.xdIncorrectEncoding(_encoding.get, None)) + case _: UnsupportedEncodingException => + error(XProcException.xdUnsupportedCharset(charset, None)) + case ex: Exception => + error(ex) + } + } else { + error(XProcException.xsUnsupportedEncoding(_encoding.get, None)) + } + } + + if (_contentType.isEmpty) { + _contentType = Some(MediaType.XML) + } + } + + protected[xxml] def parseExpandText(node: XdmNode): Unit = { + var elem: Option[XdmNode] = Some(node) + var ename = Option.empty[QName] + var eattr = Option.empty[String] + while (elem.isDefined && eattr.isEmpty) { + elem.get.getNodeKind match { + case XdmNodeKind.ELEMENT => + if (elem.get.getNodeName.getNamespaceURI == XProcConstants.ns_p) { + ename = Some(XProcConstants._expand_text) + eattr = Option(elem.get.getAttributeValue(ename.get)) + } else { + ename = Some(XProcConstants.p_expand_text) + eattr = Option(elem.get.getAttributeValue(ename.get)) + } + case _ => () + } + elem = Option(elem.get.getParent) + } + + val value = eattr.getOrElse("true") + if (value == "true" || value == "false") { + _expandText = (value == "true") + } else { + error(XProcException.xsInvalidExpandText(ename.get, value, location)) + } + } + + protected[xxml] def parseExcludedUris(node: XdmNode): Unit = { + _excludeURIs += XProcConstants.ns_p + + var elem: Option[XArtifact] = Some(this) + var eattr = Option.empty[String] + + elem = Some(this) + while (elem.isDefined) { + if (elem.get.nodeName.getNamespaceURI == XProcConstants.ns_p) { + eattr = elem.get.attributes.get(XProcConstants._exclude_inline_prefixes) + } else { + eattr = elem.get.attributes.get(XProcConstants.p_exclude_inline_prefixes) + } + if (eattr.isDefined && elem.get.exceptions.isEmpty) { + val prefixes = mutable.HashSet.empty[String] + for (token <- eattr.get.trim.split("\\s+")) { + prefixes += token + } + + try { + _excludeURIs ++= S9Api.urisForPrefixes(node, prefixes.toSet) + } catch { + case ex: Exception => + elem.get.error(ex) + } + } + elem = elem.get.parent + } + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + val refs = mutable.HashSet.empty[QName] + + if (expandText) { + findValueTemplates() + + try { + for (avt <- _valueTemplates) { + val parser = new XValueParser(config, staticContext, XValueParser.parseAvt(avt)) + refs ++= parser.variables + _contextDependent = _contextDependent || parser.contextDependent + } + } catch { + case ex: Exception => + error(ex) + } + + if (exceptions.nonEmpty) { + return initial + } + + if (documentProperties.isDefined) { + try { + val parser = new XValueParser(config, staticContext, documentProperties.get) + refs ++= parser.variables + _contextDependent = _contextDependent || parser.contextDependent + } catch { + case ex: Exception => + error(ex) + } + } + + resolveBindings(_contextDependent, refs.toSet, initial) + + staticContext = staticContext.withConstants(initial) + } + + + + + initial + } + + private def findValueTemplates(): Unit = { + findValueTemplates(content, expandText) + } + + private def findValueTemplates(node: XdmNode, expand: Boolean): Unit = { + node.getNodeKind match { + case XdmNodeKind.DOCUMENT => + for (child <- node.axisIterator(Axis.CHILD).asScala) { + findValueTemplates(child, expand) + } + case XdmNodeKind.ELEMENT => + val inlineExpand = if (node.getNodeName.getNamespaceURI == XProcConstants.ns_p) { + Option(node.getAttributeValue(XProcConstants._inline_expand_text)) + } else { + Option(node.getAttributeValue(XProcConstants.p_inline_expand_text)) + } + val expandNode = if (inlineExpand.isDefined) { + inlineExpand.get == "true" + } else { + expand + } + if (expandNode) { + for (attr <- node.axisIterator(Axis.ATTRIBUTE).asScala) { + if (attr.getStringValue.contains("{")) { + _valueTemplates += attr.getStringValue + } + } + } + for (child <- node.axisIterator(Axis.CHILD).asScala) { + findValueTemplates(child, expandNode) + } + case XdmNodeKind.TEXT => + if (expand) { + if (node.getStringValue.contains("{")) { + _valueTemplates += node.getStringValue + } + } + case _ => () + } + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("content-type", _contentType) + attr.put("document-properties", _documentProperties) + attr.put("encoding", _encoding) + attr.put("exclude-inline-prefixes", _exclude_inline_prefixes) + attr.put("expand-text", Some(_expandText)) + dumpTree(sb, "p:inline", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XInlineLoader.scala b/src/main/scala/com/xmlcalabash/model/xxml/XInlineLoader.scala new file mode 100644 index 0000000..2ec7be4 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XInlineLoader.scala @@ -0,0 +1,44 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.params.InlineLoaderParams +import com.xmlcalabash.runtime.{StepProxy, XMLCalabashRuntime} + +class XInlineLoader(inline: XInline, parentStep: XArtifact) + extends XAtomicStep(inline.config, if (inline.contextDependent) { XProcConstants.cx_inline_loader_vt } else { XProcConstants.cx_inline_loader }) { + staticContext = inline.staticContext + _synthetic = true + parent = parentStep + + allChildren = inline.allChildren + if (inline.contextDependent) { + val xwi = new XWithInput(this, "source") + xwi.drp = inline.drp + inline.allChildren = List() + addChild(xwi) + } + + val output = new XWithOutput(this, "result") + output.validate() + addChild(output) + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val xwi = children[XWithInput] find { _.port == "source" } + val pipe = if (xwi.isDefined) { + xwi.get.children[XPipe].headOption + } else { + None + } + + val params = new InlineLoaderParams(inline.content, inline.contentType, inline.documentProperties, + inline.encoding, inline.excludeURIs, inline.expandText, pipe.isDefined, staticContext) + + val start = parent.asInstanceOf[ContainerStart] + val impl = stepImplementation + impl.configure(config, stepType, name, Some(params)) + + val proxy = new StepProxy(runtime, stepType, impl, staticContext) + runtime.addNode(this, start.addAtomic(proxy, "p:inline")) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XInput.scala b/src/main/scala/com/xmlcalabash/model/xxml/XInput.scala new file mode 100644 index 0000000..efbd2d8 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XInput.scala @@ -0,0 +1,131 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.util.MediaType + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XInput(config: XMLCalabash) extends XPort(config) { + // FIXME: support select with variables on p:input + + def this(step: XContainer, port: Option[String]) = { + this(step.config) + staticContext = step.staticContext + synthetic = true + syntheticName = XProcConstants.p_input + parent = step + if (port.isDefined) { + _port = port.get + } + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + try { + parent.get match { + case loop: XLoopingStep => + if (attributes.contains(XProcConstants._port)) { + error(XProcException.xsPortNotAllowed(attributes(XProcConstants._port), loop.stepName, location)) + } + case _ => + if (attributes.contains(XProcConstants._port)) { + _port = staticContext.parseNCName(attr(XProcConstants._port)).get + } else { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._port, None)) + } + } + + _sequence = staticContext.parseBoolean(attr(XProcConstants._sequence)) + _primary = staticContext.parseBoolean(attr(XProcConstants._primary)) + _select = attr(XProcConstants._select) + + _content_types = staticContext.parseContentTypes(attr(XProcConstants._content_types)) + if (_content_types.isEmpty) { + _content_types = List(MediaType.OCTET_STREAM) + } + + _href = attr(XProcConstants._href) + // pipe isn't allowed on p:input + } catch { + case ex: Exception => + error(ex) + } + } + + override protected[xxml] def checkEmptyAttributes(): Unit = { + super.checkEmptyAttributes() + _attrChecked = true + } + + override protected[xxml] def validate(): Unit = { + if (!_attrChecked) { + checkAttributes() + checkEmptyAttributes() + } + + super.validate() + } + + protected[xxml] def checkDefaultInputs(): Unit = { + // Any children of a p:input in this case are just default bindings; + // squirrel them away in case we need them later + if (_pipe.isDefined) { + throw XProcException.xsBadAttribute(XProcConstants._pipe, location) + } + if (children[XPipe].nonEmpty) { + throw XProcException.xsElementNotAllowed(XProcConstants.p_pipe, location) + } + + for (child <- allChildren) { + child match { + case _: XDocumentation => () + case _: XPipeinfo => () + case ds: XDataSource => + if (_href.isDefined) { + throw XProcException.xsHrefAndOtherSources(child.location) + } + ds.validate() + _defaultInputs += ds + case _ => + throw XProcException.xiThisCantHappen("Default p:input contains something that isn't an XDataSource?") + } + } + + if (_href.isDefined) { + val doc = new XDocument(this, _href.get) + _defaultInputs += doc + _href = None + } + + allChildren = List() + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + super.elaborateDefaultReadablePort(initial) + if (primary) { + Some(this) + } else { + initial + } + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + super.elaborateNameBindings(initial) + for (child <- defaultInputs) { + child.elaborateNameBindings(initial) + } + initial + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("port", Some(_port)) + attr.put("select", _select) + attr.put("primary", _primary) + attr.put("sequence", _sequence) + dumpTree(sb, "p:input", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XLibrary.scala b/src/main/scala/com/xmlcalabash/model/xxml/XLibrary.scala new file mode 100644 index 0000000..701f013 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XLibrary.scala @@ -0,0 +1,74 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import net.sf.saxon.s9api.QName + +import java.net.URI + +class XLibrary(config: XMLCalabash, val href: Option[URI]) extends XDeclContainer(config) { + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + try { + if (attributes.contains(XProcConstants._version)) { + val vstr = attr(XProcConstants._version).get + try { + _version = Some(vstr.toDouble) + } catch { + case _: NumberFormatException => + error(XProcException.xsBadVersion(vstr, None)) + } + if (_version.get != 3.0) { + error(XProcException.xsInvalidVersion(_version.get, None)) + } + } + if (_version.isEmpty && parent.isEmpty && !synthetic) { + error(XProcException.xsVersionRequired(None)) + } + } catch { + case ex: Exception => + error(ex) + } + } + + protected[xxml] def elaborateLibraryApi(): Unit = { + checkAttributes() + checkEmptyAttributes() + + for (option <- children[XOption]) { + option.checkAttributes() + option.checkEmptyAttributes() + if (_xoptions.contains(option.name)) { + error(XProcException.xsDuplicateOptionName(option.name, None)) + } else { + _xoptions.put(option.name, option) + } + } + } + + override protected[xxml] def validate(): Unit = { + // Anything to do here? + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + dumpTree(sb, "p:library", Map()) + } + + override def toString: String = { + var baseuri = if (href.isDefined) { + href.get.toString + } else { + "" + } + if (baseuri != "") { + baseuri = ": " + baseuri + } + if (stepName != tumble_id) { + s"${staticContext.nodeName}(${stepName};${tumble_id})${baseuri}" + } else { + s"${staticContext.nodeName}(${stepName})${baseuri}" + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XLoopingStep.scala b/src/main/scala/com/xmlcalabash/model/xxml/XLoopingStep.scala new file mode 100644 index 0000000..729e271 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XLoopingStep.scala @@ -0,0 +1,175 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.util.MediaType + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +abstract class XLoopingStep(config: XMLCalabash) extends XContainer(config) { + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + val seenPorts = mutable.HashSet.empty[String] + val newChildren = ListBuffer.empty[XArtifact] + + var primaryInput = Option.empty[XWithInput] + var primaryOutput = Option.empty[XOutput] + + for (input <- children[XWithInput]) { + input.validate() + if (primaryInput.isDefined) { + error(XProcException.xsInvalidPipeline(s"At most one p:with-input is allowed on loops", location)) + } else { + primaryInput = Some(input) + newChildren += input + } + } + + if (primaryInput.isEmpty && !this.isInstanceOf[XForLoop]) { + val xwi = new XWithInput(this, "#anon", true, true, MediaType.MATCH_ANY) + xwi.validate() + primaryInput = Some(xwi) + newChildren += xwi + } + + val current = new XInput(this, Some("current")) + current.sequence = false + current.primary = true + current.contentTypes = MediaType.MATCH_ANY + current.validate() + newChildren += current + seenPorts += "current" + + for (output <- children[XOutput]) { + output.validate() + if (seenPorts.contains(output.port)) { + output.error(XProcException.xsDupPortName(output.port, None)) + } else { + seenPorts += output.port + if (output.primary) { + if (primaryOutput.isDefined) { + output.error(XProcException.xsDupPrimaryInputPort(output.port, primaryInput.get.port, None)) + } else { + primaryOutput = Some(output) + } + } + newChildren += output + } + } + + //val newScope = checkStepNameScoping(inScopeNames) + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XWithInput => () + case _: XOutput => () + case v: XVariable => + v.validate() + newChildren += v + case step: XStep => + step.validate() + newChildren += step + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + allChildren = newChildren.toList + + if (children[XOutput].length == 1) { + val output = children[XOutput].head + if (!output.primarySpecified) { + output.primary = true + } + } + + constructDefaultOutput() + + this match { + case _: XForWhile => constructTestOutput() + case _: XForUntil => constructTestOutput() + case _ => () + } + } + + private def constructTestOutput(): Unit = { + var testOutput = Option.empty[XOutput] + for (child <- children[XOutput]) { + if (child.port == "test") { + testOutput = Some(child) + } + } + + if (testOutput.isEmpty) { + val output = new XOutput(this, Some("test")) + output.primary = false + output.sequence = false + output.contentTypes = MediaType.MATCH_ANY + + val firstStep = children[XStep].head + val step = children[XStep].last + val pout = step.primaryOutput + if (pout.isDefined) { + val pipe = new XPipe(step.primaryOutput.get) + output.addChild(pipe) + if (Option(firstStep).isDefined) { + insertBefore(output, firstStep) + } else { + addChild(output) + } + } + } + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + children[XWithInput] foreach { _.elaborateDefaultReadablePort(initial) } + + var curdrp: Option[XPort] = Some(children[XInput].head) + for (child <- allChildren) { + child match { + case _: XWithInput => + () // Don't make this one point to current! + case _ => + curdrp = child.elaborateDefaultReadablePort(curdrp) + } + } + + children[XOutput] find { _.primary } + } + + override protected def addInputFilter(child: XPort, filter: XStep): Unit = { + val container = parent.get match { + case cont: XContainer => cont + case _ => + error(XProcException.xiThisCantHappen("Parent of filtering step isn't a container?")) + return + } + + val filterxwi = new XWithInput(this, "source", true, true, MediaType.MATCH_ANY) + filterxwi.allChildren = child.allChildren + + val filterxwo = new XWithOutput(filter, "result") + + val stepxwi = new XWithInput(this, "source") + + val pipe = new XPipe(stepxwi, filter.stepName, "result") + + stepxwi.addChild(pipe) + insertBefore(stepxwi, child) + removeChild(child) + + stepxwi.validate() + + filter.addChild(filterxwi) + filter.addChild(filterxwo) + + container.insertBefore(filter, this) + filterxwi.validate() + filterxwo.validate() + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XMLStaticContext.scala b/src/main/scala/com/xmlcalabash/model/xxml/XMLStaticContext.scala new file mode 100644 index 0000000..df6cc30 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XMLStaticContext.scala @@ -0,0 +1,56 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.Location +import com.xmlcalabash.util.{MinimalStaticContext, S9Api, URIUtils, Urify, XdmLocation} +import net.sf.saxon.s9api.{QName, XdmNode} + +import java.net.URI +import scala.collection.mutable + +class XMLStaticContext(val nodeName: QName, location: Option[Location]) extends XStaticContext() { + _location = location + + def this(nodeName: QName, location: Location) = { + this(nodeName, Some(location)) + } + + def this(nodeName: QName, location: Location, nsmap: Map[String,String]) = { + this(nodeName, Some(location)) + _inscopeNamespaces ++= nsmap + } + + def this(node: XdmNode) = { + this(node.getNodeName, XdmLocation.from(node)) + _inscopeNamespaces ++= S9Api.inScopeNamespaces(node) + } + + def forNode(node: XdmNode): XMLStaticContext = { + val newcontext = new XMLStaticContext(node) + newcontext._config = _config + newcontext._inscopeConstants ++= _inscopeConstants + newcontext._inscopeNamespaces ++= _inscopeNamespaces + newcontext._baseURI = Option(node.getBaseURI) + newcontext + } + + /* + def withConstants(bcontext: XNameBindingContext): XMLStaticContext = { + if (bcontext.inScopeConstants.isEmpty) { + return this + } + + val newContext = new XMLStaticContext(nodeName, location) + newContext._inscopeConstants ++= inscopeConstants + newContext._inscopeNamespaces ++= inscopeNamespaces + newContext._baseURI = _baseURI + newContext._location = _location + + for ((name,binding) <- bcontext.inScopeConstants) { + newContext._inscopeConstants.put(name, binding) + } + + newContext + } + + */ +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XNameBinding.scala b/src/main/scala/com/xmlcalabash/model/xxml/XNameBinding.scala new file mode 100644 index 0000000..3bb4542 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XNameBinding.scala @@ -0,0 +1,407 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.messages.Message +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.model.util.{XProcConstants, XValueParser} +import com.xmlcalabash.runtime.{XMLCalabashRuntime, XProcVtExpression, XProcXPathExpression} +import com.xmlcalabash.steps.internal.ValueComputation +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api, TypeUtils} +import net.sf.saxon.ma.map.{MapItem, MapType} +import net.sf.saxon.om.StructuredQName +import net.sf.saxon.s9api.{QName, SequenceType, XdmAtomicValue, XdmMap} + +import java.net.URISyntaxException +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +object XNameBinding { + def checkValueTokens(config: XMLCalabash, + context: MinimalStaticContext, + values: Option[String]): Option[List[XdmAtomicValue]] = { + if (values.isDefined) { + val exprEval = config.expressionEvaluator.newInstance() + + val expr = new XProcXPathExpression(context, values.get, None, None, None) + val value = exprEval.value(expr, List(), Map(), None) + val iter = value.item.iterator() + val allowed = ListBuffer.empty[XdmAtomicValue] + while (iter.hasNext) { + val token = iter.next() + token match { + case atom: XdmAtomicValue => + allowed += atom + case _ => + throw XProcException.xsInvalidValues(values.get, None) + } + } + + return Some(allowed.toList) + } + + None + } + + def promotedValue(config: XMLCalabash, + name: QName, + declaredType: Option[SequenceType], + tokenList: Option[List[XdmAtomicValue]], + staticValueMsg: XdmValueItemMessage): XdmValueItemMessage = { + val typeUtils = new TypeUtils(config, staticValueMsg.context) + try { + typeUtils.convertType(name, staticValueMsg, declaredType, tokenList) + } catch { + case ex: XProcException => + if (ex.code == XProcException.errxd(19)) { + throw ex + } else { + // FIXME: if this was a namespace error, we lose critical information about the error + throw XProcException.xdBadType(name, staticValueMsg.item.toString, declaredType.get.toString, None) + } + case ex: URISyntaxException => + val dtype = declaredType.get.getItemType.getTypeName + if (dtype == XProcConstants.xs_anyURI) { + throw XProcException.xdInvalidURI(staticValueMsg.item.toString, None) + } else { + throw ex + } + case ex: Exception => + throw ex + } + } +} + +abstract class XNameBinding(config: XMLCalabash) extends XArtifact(config) { + private val structured_xs_QName = new StructuredQName("xs", XProcConstants.ns_xs, "QName") + + protected var _qnameKeys: Boolean = false + protected var _name: QName = _ + protected var _as = Option.empty[String] + protected var _declaredType = Option.empty[SequenceType] + protected var _values = Option.empty[String] + protected var _static = Option.empty[Boolean] + protected var _constant = false + protected var _required = Option.empty[Boolean] + protected var _select = Option.empty[String] + protected var _avt = Option.empty[String] + protected var _visibility = Option.empty[String] + protected var _variableReferences: Set[QName] = Set() + + protected var _constantValue = Option.empty[XdmValueItemMessage] + protected var _computeValue = Option.empty[ValueComputation] + protected var collection = false + + protected var _href = Option.empty[String] + protected var _pipe = Option.empty[String] + + private var _drp: Option[XPort] = None + + def name: QName = _name + + protected[xxml] def drp: Option[XPort] = _drp + + protected[xxml] def drp_=(port: Option[XPort]): Unit = { + _drp = port + } + + def static: Boolean = _static.getOrElse(false) + def constant: Boolean = _constant + def visibility: String = _visibility.getOrElse("public") + + def constantValue: Option[XdmValueItemMessage] = _constantValue + + def as: Option[String] = _as + def declaredType: Option[SequenceType] = _declaredType + + def required: Boolean = _required.getOrElse(false) + + def select: Option[String] = _select + + def qnameKeys: Boolean = _qnameKeys + + def usedByPipeline: Boolean = { + children[XWithOutput].head.readBy.nonEmpty + } + + override protected[xxml] def checkAttributes(): Unit = { + if (synthetic) { + return + } + + super.checkAttributes() + + try { + if (attributes.contains(XProcConstants._name)) { + val name = attr(XProcConstants._name).get + try { + _name = staticContext.parseQName(name) + } catch { + case ex: XProcException => + if (ex.code == XProcException.err_xd0015) { + error(XProcException.xsOptionUndeclaredNamespace(name, ex.location)) + } else { + error(ex) + } + } + + this match { + case _: XWithOption => + () // This would be ok if the step has an option declared in the p: namespace + case _ => + if (_name.getNamespaceURI == XProcConstants.ns_p) { + error(XProcException.xsOptionInXProcNamespace(_name, None)) + } + } + } else { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._name, None)) + } + + _as = attr(XProcConstants._as) + _declaredType = staticContext.parseSequenceType(_as, config.itemTypeFactory) + + if (declaredType.isDefined) + declaredType.get.getUnderlyingSequenceType.getPrimaryType match { + case map: MapType => + if (map.getKeyType.getPrimitiveItemType.getTypeName == structured_xs_QName) { + // We have to lie about the type of maps with QName keys because we're + // going to allow users to put strings in there. + _qnameKeys = true + _declaredType = Some(staticContext.parseFakeMapSequenceType(as.get, config.itemTypeFactory)) + } + case _ => () + } + + _static = staticContext.parseBoolean(attr(XProcConstants._static)) + _required = staticContext.parseBoolean(attr(XProcConstants._required)) + _select = attr(XProcConstants._select) + _visibility = attr(XProcConstants._visibility) + + if (visibility != "public" && visibility != "private") { + error(XProcException.xsBadVisibility(visibility, location)) + } + + if (_required.isDefined && _required.get && _select.isDefined) { + error(XProcException.xsRequiredAndDefaulted(_name, location)) + } + + if (_required.isDefined && _required.get && static) { + error(XProcException.xsRequiredAndStatic(_name, location)) + } + + val _collection = attr(XProcConstants._collection) + if (_collection.isDefined) { + val coll = _collection.get + if (coll == "true" || coll == "false") { + collection = coll == "true" + } else { + error(XProcException.xsBadTypeValue(coll, "xs:boolean", None)) + } + } + } catch { + case ex: Exception => + error(ex) + } + } + + protected def checkValueTokens: Option[List[XdmAtomicValue]] = { + _values = attr(XProcConstants._values) + if (_values.isDefined) { + val exprEval = config.expressionEvaluator.newInstance() + + val expr = new XProcXPathExpression(staticContext, _values.get, None, None, None) + val value = exprEval.value(expr, List(), Map(), None) + val iter = value.item.iterator() + val allowed = ListBuffer.empty[XdmAtomicValue] + while (iter.hasNext) { + val token = iter.next() + token match { + case atom: XdmAtomicValue => + allowed += atom + case _ => + error(XProcException.xsInvalidValues(_values.get, None)) + } + } + + return Some(allowed.toList) + } + + None + } + + protected def valueParser(): XValueParser = { + if (_avt.isDefined) { + new XValueParser(config, staticContext, XValueParser.parseAvt(_avt.get)) + } else if (_select.isDefined) { + new XValueParser(config, staticContext, _select.get) + } else { + throw XProcException.xsInvalidPipeline(s"No value provided for ${name}", location) + } + } + + protected[xxml] def graphEdges(runtime: XMLCalabashRuntime): Unit = { + if (constantValue.isDefined) { + return + } + + for (input <- children[XWithInput]) { + for (child <- input.allChildren) { + child match { + case pipe: XPipe => + pipe.graphEdges(runtime) + case _: XWithOutput => + () + case _ => + throw XProcException.xiThisCantHappen(s"Name binding has unexpected child: ${child}") + } + } + } + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + super.elaborateDefaultReadablePort(initial) + _drp + } + + override protected[xxml] def elaboratePortConnections(): Unit = { + // This smells bad. Clearly it's defending against the case where there are no XDataSources + // in the children but there's a DRP. Except in the case of variable, if there is an XDataSource, + // it'll be in a XWithInput. So why are there ever "naked" XDataSource elements in the + // children? + if (drp.isDefined && children[XDataSource].isEmpty && children[XWithInput].isEmpty) { + addChild(new XPipe(drp.get)) + } + + super.elaboratePortConnections() + + // Now collect any unwrapped data sources under a p:with-input + val newChildren = ListBuffer.empty[XArtifact] + val input = if (children[XWithInput].isEmpty) { + val xwi = new XWithInput(this, "source") + xwi.sequence = true + xwi.primary = false + xwi.contentTypes = MediaType.MATCH_ANY + xwi + } else { + children[XWithInput].head + } + newChildren += input + + for (child <- allChildren) { + child match { + case xwi: XWithInput => + if (xwi ne input) { + throw XProcException.xiThisCantHappen("p:with-option has more than one p:with-input child?") + } + case ds: XDataSource => + input.addChild(ds) + case _ => + newChildren += child + } + } + + allChildren = newChildren.toList + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + var bcontext = initial + + if (_avt.isDefined || _select.isDefined) { + try { + val parser = valueParser() + _variableReferences = parser.variables + val contextDependent = parser.contextDependent + + _constant = !(this.isInstanceOf[XOption]) // p:options can never be constants + val namepipe = ListBuffer.empty[XNameBinding] + for (ref <- _variableReferences) { + val cbind = initial.inScopeConstants.get(ref) + val dbind = initial.inScopeDynamics.get(ref) + if (cbind.isDefined) { + // ok + } else if (dbind.isDefined) { + _constant = false + dbind.get match { + case v: XVariable => + namepipe += v + case opt: XOption => + namepipe += opt + case _ => + error(XProcException.xiThisCantHappen(s"Unexpected name binding: ${dbind.get}")) + } + } else { + error(XProcException.xsNoBindingInExpression(ref, None)) + } + } + + if (exceptions.nonEmpty) { + return initial + } + + if (contextDependent) { + _constant = false + } else { + drp = None // irrelevant + } + + if (constant) { + val expr = if (_avt.isDefined) { + new XProcVtExpression(staticContext, XValueParser.parseAvt(_avt.get), true) + } else { + new XProcXPathExpression(staticContext, _select.get) + } + + val bindings = mutable.HashMap.empty[String,Message] + for ((name,value) <- initial.inScopeConstants) { + bindings.put(name.getClarkName, value.constantValue.get) + } + + var computed = config.expressionEvaluator.value(expr, List(), bindings.toMap, None) + if (qnameKeys) { + computed.item match { + case xmap: XdmMap => + val qnameMap = S9Api.forceQNameKeys(xmap.getUnderlyingValue, computed.context) + computed = new XdmValueItemMessage(qnameMap, computed.metadata, computed.context) + case xmap: MapItem => + val qnameMap = S9Api.forceQNameKeys(xmap, computed.context) + computed = new XdmValueItemMessage(qnameMap, computed.metadata, computed.context) + case _ => + throw XProcException.xiThisCantHappen(s"Non-map item has qnameKeys: ${computed.item}") + } + } + + _constantValue = Some(promotedStaticValue(computed)) + bcontext = bcontext.withBinding(this) + } else { + if (namepipe.nonEmpty) { + val xwi = new XWithInput(this, "#bindings") + xwi.sequence = true + xwi.primary = false + xwi.contentTypes = MediaType.MATCH_ANY + addChild(xwi) + for (binding <- namepipe) { + val xstep = bcontext.inScopeDynamics.get(binding.name) + val pipe = new XPipe(xwi, xstep.get.tumble_id, "result") + xwi.addChild(pipe) + } + } + } + } catch { + case ex: Exception => + error(ex) + } + } + + super.elaborateNameBindings(initial) + + bcontext + } + + protected def promotedStaticValue(staticValueMsg: XdmValueItemMessage): XdmValueItemMessage = { + val sig = stepDeclaration + val optdecl = sig.get.option(name).get + + XNameBinding.promotedValue(config, name, optdecl.declaredType, optdecl.tokenList, staticValueMsg) + } +} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XNameBindingContext.scala b/src/main/scala/com/xmlcalabash/model/xxml/XNameBindingContext.scala new file mode 100644 index 0000000..ea50a8f --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XNameBindingContext.scala @@ -0,0 +1,47 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.XdmValueItemMessage +import net.sf.saxon.s9api.QName + +import scala.collection.mutable + +class XNameBindingContext private(val inScopeStatics: Set[QName], + val inScopeConstants: Map[QName, XNameBinding], + val inScopeDynamics: Map[QName, XNameBinding]) { + def this() = { + this(Set(), Map(), Map()) + } + + def withBinding(binding: XNameBinding): XNameBindingContext = { + if (inScopeStatics.contains(binding.name)) { + throw XProcException.xsShadowsStatic(binding.name, binding.location) + } + + val statics = mutable.Set.empty[QName] ++ inScopeStatics + if (binding.static) { + statics += binding.name + } + + val constants = mutable.HashMap.empty[QName, XNameBinding] ++= inScopeConstants + val dynamics = mutable.HashMap.empty[QName, XNameBinding] ++= inScopeDynamics + + if (binding.constant) { + constants.put(binding.name, binding) + } else { + dynamics.put(binding.name, binding) + } + + new XNameBindingContext(statics.toSet, constants.toMap, dynamics.toMap) + } + + def onlyStatics: XNameBindingContext = { + val constants = mutable.HashMap.empty[QName, XNameBinding] + for ((name, value) <- inScopeConstants) { + if (inScopeStatics.contains(name)) { + constants.put(name, value) + } + } + new XNameBindingContext(inScopeStatics, constants.toMap, Map()) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XNamePipe.scala b/src/main/scala/com/xmlcalabash/model/xxml/XNamePipe.scala new file mode 100644 index 0000000..2066663 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XNamePipe.scala @@ -0,0 +1,22 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.model.util.SaxonTreeBuilder +import com.xmlcalabash.runtime.XMLCalabashRuntime + +import scala.collection.mutable + +class XNamePipe(config: XMLCalabash, val binding: XNameBinding) extends XArtifact(config) { + staticContext = binding.staticContext + + def this(binding: XNameBinding) = { + this(binding.config, binding) + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("name", Some(binding.name)) + attr.put("ref", Some(binding.tumble_id)) + dumpTree(sb, "p:name-pipe", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XNamedArtifact.scala b/src/main/scala/com/xmlcalabash/model/xxml/XNamedArtifact.scala new file mode 100644 index 0000000..9fc210e --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XNamedArtifact.scala @@ -0,0 +1,5 @@ +package com.xmlcalabash.model.xxml + +trait XNamedArtifact { + def stepName: String +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XOption.scala b/src/main/scala/com/xmlcalabash/model/xxml/XOption.scala new file mode 100644 index 0000000..fb90a18 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XOption.scala @@ -0,0 +1,140 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.runtime.params.XPathBindingParams +import com.xmlcalabash.runtime.{XMLCalabashRuntime, XProcMetadata, XProcXPathExpression} +import com.xmlcalabash.util.{TypeUtils, XProcVarValue} +import net.sf.saxon.s9api.{QName, SaxonApiException, XdmAtomicValue} +import net.sf.saxon.trans.XPathException + +import scala.collection.mutable + +class XOption(config: XMLCalabash) extends XNameBinding(config) with XGraphableArtifact { + private var _allowedValues = Option.empty[List[XdmAtomicValue]] + private var _runtimeBindings = Map.empty[QName, XProcVarValue] + private var _cx_as = Option.empty[String] + + def this(config: XMLCalabash, name: QName, value: XdmValueItemMessage) = { + this(config) + _name = name + _constantValue = Some(value) + _synthetic = true + _constant = true + staticContext = new XArtifactContext(this, value.context, XProcConstants.p_option) + } + + def tokenList: Option[List[XdmAtomicValue]] = _allowedValues + + def allowedValues: Option[List[XdmAtomicValue]] = _allowedValues + + override def usedByPipeline: Boolean = { + // always evaluate these so we find static errors + true + } + + def runtimeBindings(bindings: Map[QName, XProcVarValue]): Unit = { + _runtimeBindings = bindings + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + _cx_as = attr(XProcConstants.cx_as) + _allowedValues = checkValueTokens + } + + override protected[xxml] def validate(): Unit = { + if (!static) { + parent.get match { + case _: XLibrary => + error(XProcException.xsOptionMustBeStatic(name, None)) + case _ => () + } + } + + for (child <- allChildren) { + child match { + case _: XPipeinfo => + case _: XDocumentation => + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + allChildren = List() + + val xwo = new XWithOutput(this, "result") + addChild(xwo) + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + super.elaborateNameBindings(initial) + + var newContext = initial + if (static) { + _constant = true + _constantValue = Some(config.staticOptions(name)) + newContext = initial.withBinding((this)) + } else { + newContext = initial.withBinding(this) + if (select.isDefined) { + val compiler = config.processor.newXPathCompiler() + for (name <- _variableReferences) { + compiler.declareVariable(name) + } + for ((prefix,uri) <- staticContext.inscopeNamespaces) { + compiler.declareNamespace(prefix, uri) + } + try { + compiler.compile(select.get) + } catch { + /* + case ex: SaxonApiException => + throw XProcException.xsStaticErrorInExpression(select.get, ex.getMessage, location) + case ex: Exception => + throw ex + */ + case ex: Exception => + logger.debug(ex.getMessage) + } + } + } + + newContext + } + + override protected[xxml] def elaboratePortConnections(): Unit = { + // p:option doesn't have any port connections + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + if (!static && usedByPipeline) { + val params = new XPathBindingParams(false) + val expr = new XProcXPathExpression(staticContext, _select.getOrElse("()"), declaredType, _allowedValues, params) + val start = parent.asInstanceOf[ContainerStart] + runtime.addNode(this, start.addOption(name.getClarkName, expr, params, true)) + } + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + if (Option(_name).isDefined) { + attr.put("name", Some(_name.getEQName)) + } + + if (constantValue.isDefined) { + attr.put("constant-value", Some(constantValue.get.item.toString)) + } else { + attr.put("select", _select) + attr.put("avt", _avt) + } + + attr.put("as", _as) + attr.put("required", _required) + attr.put("visiblity", _visibility) + dumpTree(sb, "p:option", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XOtherwise.scala b/src/main/scala/com/xmlcalabash/model/xxml/XOtherwise.scala new file mode 100644 index 0000000..8ee5db5 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XOtherwise.scala @@ -0,0 +1,71 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants + +import scala.collection.mutable + +class XOtherwise(config: XMLCalabash) extends XChooseBranch(config) { + def this(choose: XChoose) = { + this(choose.config) + staticContext = choose.staticContext + parent = choose + synthetic = true + syntheticName = XProcConstants.p_otherwise + _test = "true()" + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + _test = "true()" + } + + override def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + var seenPipeline = false + + //val newScope = checkStepNameScoping(inScopeNames) + for (child <- allChildren) { + child.validate() + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XOutput => + if (seenPipeline) { + error(XProcException.xsInvalidPipeline("p:output cannot follow steps in p:otherwise", location)) + } + case _: XVariable => + seenPipeline = true + case _: XStep => + seenPipeline = true + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + if (children[XOutput].length == 1) { + val output = children[XOutput].head + if (!output.primarySpecified) { + output.primary = true + } + } + + constructDefaultOutput() + + orderChildren() + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + if (synthetic && initial.isEmpty) { + // Special case, return an empty sequence + val step = children[XStep].head + val input = step.children[XWithInput].head + input.addChild(new XEmpty(input)) + } + + super.elaborateDefaultReadablePort(initial) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XOutput.scala b/src/main/scala/com/xmlcalabash/model/xxml/XOutput.scala new file mode 100644 index 0000000..65c5b2b --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XOutput.scala @@ -0,0 +1,181 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.runtime.XProcXPathExpression +import com.xmlcalabash.util.{MediaType, S9Api} +import net.sf.saxon.s9api.{QName, SaxonApiException, XdmMap} + +import scala.collection.mutable +import scala.jdk.CollectionConverters.MapHasAsScala + +class XOutput(config: XMLCalabash) extends XPort(config) { + private var serializationExpr = Option.empty[String] + private val _serialization = mutable.Map.empty[QName,String] + + def this(step: XContainer, port: Option[String]) = { + this(step.config) + staticContext = step.staticContext + parent = step + synthetic = true + syntheticName = XProcConstants.p_output + if (port.isDefined) { + _port = port.get + } + _attrChecked = true + } + + def serialization: Map[QName,String] = { + _serialization.toMap + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + if (parent.isDefined) { + parent.get match { + case _: XDeclareStep => + checkDeclareStepAttributes() + case _ => + checkCompoundStepAttributes() + } + } else { + checkCompoundStepAttributes() // This can't happen + } + } + + override protected[xxml] def checkEmptyAttributes(): Unit = { + super.checkEmptyAttributes() + _attrChecked = true + } + + private def checkDeclareStepAttributes(): Unit = { + checkCompoundStepAttributes() + serializationExpr = attr(XProcConstants._serialization) + } + + private def checkCompoundStepAttributes(): Unit = { + try { + if (attributes.contains(XProcConstants._port)) { + _port = staticContext.parseNCName(attr(XProcConstants._port)).get + } else { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._port, None)) + } + + _sequence = staticContext.parseBoolean(attr(XProcConstants._sequence)) + _primary = staticContext.parseBoolean(attr(XProcConstants._primary)) + _content_types = staticContext.parseContentTypes(attr(XProcConstants._content_types)) + if (_content_types.isEmpty) { + _content_types = List(MediaType.OCTET_STREAM) + } + + _href = attr(XProcConstants._href) + _pipe = attr(XProcConstants._pipe) + } catch { + case ex: Exception => + error(ex) + } + } + + override protected[xxml] def validate(): Unit = { + if (!_attrChecked) { + checkAttributes() + checkEmptyAttributes() + } + super.validate() + + if (parent.isDefined) { + parent.get match { + case decl: XDeclareStep => + if (decl.atomic && children[XDataSource].nonEmpty) { + if (decl.stepType.isDefined) { + error(XProcException.xsAtomicOutputWithBinding(port, decl.stepType.get, location)) + } else { + error(XProcException.xsAtomicOutputWithBinding(port, location)) + } + } + case _ => + () + } + } + + if (serializationExpr.isDefined) { + val exprEval = config.expressionEvaluator.newInstance() + val expr = new XProcXPathExpression(staticContext, serializationExpr.get) + val value = try { + exprEval.value(expr, List(), staticContext.inscopeConstantBindings, None) + } catch { + case sae: SaxonApiException => + throw XProcException.xsStaticErrorInExpression(serializationExpr.get, sae.getMessage, None) + } + value.item match { + case map: XdmMap => + val sermap = S9Api.forceQNameKeys(map.getUnderlyingValue, staticContext) + for ((key,value) <- sermap.asImmutableMap().asScala) { + val name = key.getQNameValue + _serialization(name) = value.getUnderlyingValue.getStringValue + } + case _ => + throw XProcException.xdValueDoesNotSatisfyType(value.item.toString, location) + } + } + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + if (parent.isDefined) { + parent.get match { + case cont: XContainer => + val steps = cont.children[XStep] + if (steps.nonEmpty) { + for (child <- allChildren) { + child.elaborateDefaultReadablePort(steps.last.primaryOutput) + } + initial + } else { + super.elaborateDefaultReadablePort(initial) + } + case _ => + super.elaborateDefaultReadablePort(initial) + } + } else { + super.elaborateDefaultReadablePort(initial) + } + } + + override def elaboratePortConnections(): Unit = { + super.elaboratePortConnections() + if (parent.isDefined) { + parent.get match { + case decl: XDeclareStep => + if (!decl.atomic) { + checkOutputBinding(decl) + } + case cont: XContainer => + checkOutputBinding(cont) + case _ => + throw XProcException.xiThisCantHappen("p:output is not a child of a container?") + } + } else { + throw XProcException.xiThisCantHappen("p:output has no parent?") + } + } + + private def checkOutputBinding(cont: XContainer): Unit = { + if (children[XDataSource].isEmpty && primary) { + val last = cont.children[XStep].last + if (last.primaryOutput.isDefined) { + val pipe = new XPipe(this, Some(last.stepName), Some(last.primaryOutput.get.port)) + addChild(pipe) + } + } + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("port", Some(_port)) + attr.put("select", _select) + attr.put("primary", _primary) + attr.put("sequence", _sequence) + dumpTree(sb, "p:output", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XParser.scala b/src/main/scala/com/xmlcalabash/model/xxml/XParser.scala new file mode 100644 index 0000000..cb68832 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XParser.scala @@ -0,0 +1,141 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.config.DocumentRequest +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.model.xxml.XParser._builtinLibraries +import com.xmlcalabash.util.MediaType +import net.sf.saxon.s9api.XdmNode +import org.slf4j.{Logger, LoggerFactory} +import org.xml.sax.InputSource + +import java.net.URI +import javax.xml.transform.sax.SAXSource +import scala.collection.mutable.ListBuffer + +object XParser { + private var _builtinLibraries: ListBuffer[XLibrary] = _ +} + +class XParser(val config: XMLCalabash) { + protected val logger: Logger = LoggerFactory.getLogger(this.getClass) + private val _exceptions = ListBuffer.empty[Exception] + + // Load all of the built in steps + if (Option(_builtinLibraries).isEmpty) { + _builtinLibraries = ListBuffer.empty[XLibrary] + + config.standardLibraryParser = true + val xpls = getClass.getClassLoader.getResources("com.xmlcalabash.library.xpl") + while (xpls.hasMoreElements) { + val xpl = xpls.nextElement() + val xmlbuilder = config.processor.newDocumentBuilder() + val stream = xpl.openStream() + val source = new SAXSource(new InputSource(stream)) + xmlbuilder.setDTDValidation(false) + xmlbuilder.setLineNumbering(true) + val libnode = xmlbuilder.build(source) + val library = loadLibrary(libnode) + + _exceptions ++= library.errors + + _builtinLibraries += library + } + + config.standardLibraryParser = false + } + + logger.debug("Built in libraries loaded") + + protected[xxml] def builtinLibraries: List[XLibrary] = _builtinLibraries.toList + def exceptions: List[Exception] = _exceptions.toList + + def exceptions(artifact: XArtifact): List[Exception] = { + if (Option(artifact).isDefined) { + _exceptions.toList ++ artifact.exceptions + } else { + _exceptions.toList + } + } + + def loadLibrary(uri: URI): XLibrary = { + val request = new DocumentRequest(uri, MediaType.XML) + val response = config.documentManager.parse(request) + if (response.contentType.xmlContentType) { + loadLibrary(response.value.asInstanceOf[XdmNode]) + } else { + throw XProcException.xsInvalidPipeline(s"Document is not XML: ${uri}", None) + } + } + + def loadLibrary(node: XdmNode): XLibrary = { + val decl = load(node) + + val library = decl match { + case lib: XLibrary => + lib + case _ => + val lib = new XLibrary(config, Option(node.getBaseURI)) + lib.staticContext = new XArtifactContext(lib, node) + lib.synthetic = true + lib.syntheticName = XProcConstants.p_library + lib.addChild(decl) + lib + } + + library.xelaborate() + library + } + + def loadDeclareStep(uri: URI): XDeclareStep = { + val request = new DocumentRequest(uri, MediaType.XML) + val response = config.documentManager.parse(request) + if (response.contentType.xmlContentType) { + loadDeclareStep(response.value.asInstanceOf[XdmNode]) + } else { + throw XProcException.xsInvalidPipeline(s"Document is not XML: ${uri}", None) + } + } + + def loadDeclareStep(node: XdmNode): XDeclareStep = { + val decl = load(node) + + decl match { + case step: XDeclareStep => + step.xelaborate() + step + case _ => + throw XProcException.xiUserError(s"Pipeline document did not contain a p:declare-step.") + } + } + + private def load(node: XdmNode): XDeclContainer = { + val hier = NodeHierarchy.newInstance(config, node) + val loader = new Loader(this, hier) + _exceptions ++= loader.exceptions + + if (!hier.useWhen(hier.root)) { + _exceptions += XProcException.xsInvalidPipeline("Root element use-when is false; no document", None) + } + + if (_exceptions.nonEmpty) { + throw _exceptions.head + } + + val decl = if (loader.declaredStep.isEmpty) { + loader.library.get + } else { + loader.declaredStep.get + } + + decl.builtinLibraries = _builtinLibraries.toList + _exceptions ++= decl.errors + + if (_exceptions.nonEmpty) { + throw _exceptions.head + } + + decl + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XPipe.scala b/src/main/scala/com/xmlcalabash/model/xxml/XPipe.scala new file mode 100644 index 0000000..e4f9446 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XPipe.scala @@ -0,0 +1,227 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.runtime.XMLCalabashRuntime + +import scala.collection.mutable + +class XPipe(config: XMLCalabash) extends XDataSource(config) { + private var _step = Option.empty[String] + private var _port = Option.empty[String] + + private var _fromPort: XPort = _ + private var _fromStep: XArtifact = _ + private var _toStep: XArtifact = _ + + def step: Option[String] = _step + def port: Option[String] = _port + def from: Option[XPort] = Option(_fromPort) + + def this(parent: XArtifact, step: Option[String], port: Option[String]) = { + this(parent.config) + this.parent = parent + staticContext = parent.staticContext + _synthetic = true + _step = step + _port = port + } + + def this(parent: XArtifact, step: String, port: String) = { + this(parent, Some(step), Some(port)) + } + + // This constructor works even if we don't subsequently attempt to validate the connection + def this(parent: XArtifact, originPort: XPort) = { + this(parent.config) + this.parent = parent + staticContext = parent.staticContext + _synthetic = true + _fromPort = originPort + _fromStep = originPort.ancestorNode.get + _step = Some(originPort.parent.get.asInstanceOf[XStep].stepName) + _port = Some(originPort.port) + _toStep = parent.ancestorNode.get + } + + def this(port: XPort) = { + this(port.config) + val pstep = port.ancestorStep + staticContext = port.staticContext + _synthetic = true + _step = Some(pstep.get.stepName) + _port = Some(port.port) + } + + def this(pipe: XPipe) = { + this(pipe.config) + staticContext = pipe.staticContext + _synthetic = true + _step = pipe._step + _port = pipe._port + } + + protected[xxml] def graphEdges(runtime: XMLCalabashRuntime): Unit = { + // FIXME: make handling of dropped variables more robust + var connect = true + _toStep match { + case bind: XNameBinding => + connect = bind.usedByPipeline + case _ => + () + } + + if (connect) { + val fromNode = runtime.node(_fromStep) + val toNode = runtime.node(_toStep) + + parent.get match { + case xport: XPort => + var sport = xport.port + var rport = port.get + + if (sport == "#anon") { + sport = "source" + if (_toStep.ancestorOf(_fromStep)) { + sport = "#anon_result" + } + } + + if (rport == "#anon") { + rport = "#anon_result" + } + + if (_fromStep eq _toStep) { + // This is a special case that the Graph doesn't catch. + // FIXME: fix this bug in the graph construction code + + val ps = _fromPort.parent.get + ps match { + case _: XContainer => + () // this is ok + case _ => + throw XProcException.xsLoop(ps.asInstanceOf[XStep].stepName, rport, location) + } + } + + //println(s"Edge from ${_fromStep}/${rport} to ${_toStep}/${sport}") + runtime.graph.addOrderedEdge(fromNode, rport, toNode, sport) + case _: XNameBinding => + //println(s"Edge from ${_fromStep}/${port.get} to ${_toStep}/source") + runtime.graph.addOrderedEdge(fromNode, port.get, toNode, "source") + } + } + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + try { + if (attributes.contains(XProcConstants._step)) { + _step = Some(staticContext.parseNCName(attr(XProcConstants._step).get)) + } + if (attributes.contains(XProcConstants._port)) { + _port = Some(staticContext.parseNCName(attr(XProcConstants._port).get)) + } + } catch { + case ex: Exception => + error(ex) + } + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + for (child <- allChildren) { + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + allChildren = List() + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + initial + } + + override protected[xxml] def elaboratePortConnections(): Unit = { + if (step.isEmpty) { + if (drp.isEmpty) { + error(XProcException.xsPipeWithoutStepOrDrp(location)) + } else { + drp.get.parent.get match { + case xstep: XStep => + _step = Some(xstep.stepName) + case _ => + error(XProcException.xiThisCantHappen("Parent of drp is not a step?")) + } + } + } + } + + override protected[xxml] def elaborateValidatePortConnections(ports: XPortBindingContext): Unit = { + val gparent = if (parent.isDefined) { + parent.get.parent + } else { + None + } + + val internal = if (gparent.isDefined) { + gparent.get match { + case _: XChoose => true + case _ => false + } + } else { + false + } + + val from = if (port.isEmpty) { + ports.primaryPort(_step.get) + } else { + if (internal) { + ports.privatePort(this) + } else { + ports.port(this) + } + } + + if (from.isEmpty) { + if (port.isEmpty) { + error(XProcException.xsPortNotReadableNoPrimaryInput(_step.get, location)) + } else { + error(XProcException.xsPortNotReadable(_step.get, _port.get, location)) + } + return + } + + _fromPort = from.get + _fromStep = from.get.ancestorNode.get + _port = Some(_fromPort.port) + _toStep = parent.get.ancestorNode.get + } + + override protected[xxml] def computeReadsFrom(): Unit = { + super.computeReadsFrom() + val step = _fromPort.parent.get + for (child <- step.children[XWithOutput]) { + if (child.port == _fromPort.port) { + child.readBy = _toStep + } + } + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("step", _step) + attr.put("port", _port) + dumpTree(sb, "p:pipe", attr.toMap) + } + + override def toString: String = { + s"from ${step.getOrElse("(drp step)")}/${port.getOrElse("(primary output)")}" + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XPipeinfo.scala b/src/main/scala/com/xmlcalabash/model/xxml/XPipeinfo.scala new file mode 100644 index 0000000..3048b69 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XPipeinfo.scala @@ -0,0 +1,15 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.model.util.SaxonTreeBuilder +import net.sf.saxon.s9api.XdmNode + +class XPipeinfo(config: XMLCalabash, val content: XdmNode) extends XArtifact(config) { + override protected[xxml] def validate(): Unit = { + // nop + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + dumpTree(sb, "p:pipeinfo", Map(), "« content elided »") + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XPort.scala b/src/main/scala/com/xmlcalabash/model/xxml/XPort.scala new file mode 100644 index 0000000..ddcdf80 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XPort.scala @@ -0,0 +1,91 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.runtime.XMLCalabashRuntime +import com.xmlcalabash.util.MediaType + +import scala.collection.mutable.ListBuffer + +abstract class XPort(config: XMLCalabash) extends XArtifact(config) { + protected var _port: String = "#anon" + protected var _sequence = Option.empty[Boolean] + protected var _primary = Option.empty[Boolean] + protected var _select = Option.empty[String] + protected var _selectBindings = Option.empty[XWithInput] + protected var _content_types = List.empty[MediaType] + protected val _defaultInputs: ListBuffer[XDataSource] = ListBuffer.empty[XDataSource] + // XInput and XOutput on p:declare-step are checked when the step API is + // computed. Elsewhere, they need to be checked during validation. + protected var _attrChecked = false + private var _irrelevant = false + + protected var _href = Option.empty[String] + protected var _pipe = Option.empty[String] + + private var _drp: Option[XPort] = None + + def portSpecified: Boolean = !_port.startsWith("#") + + def port: String = _port + protected[xxml] def port_=(name: String): Unit = { + _port = name + } + def sequence: Boolean = _sequence.getOrElse(false) + protected[xxml] def sequence_=(seq: Boolean): Unit = { + _sequence = Some(seq) + } + def primary: Boolean = _primary.getOrElse(false) + def primarySpecified: Boolean = _primary.nonEmpty + protected[xxml] def primary_=(primary: Boolean): Unit = { + _primary = Some(primary) + } + def select: Option[String] = _select + protected[xxml] def selectBindings: Option[XWithInput] = _selectBindings + def contentTypes: List[MediaType] = _content_types + protected[xxml] def contentTypes_=(types: List[MediaType]): Unit = { + _content_types = types + } + + def irrelevant: Boolean = _irrelevant + protected[xxml] def irrelevant_=(irrelevant: Boolean): Unit = { + _irrelevant = irrelevant + } + + def defaultInputs: List[XDataSource] = _defaultInputs.toList + + protected[xxml] def drp: Option[XPort] = _drp + protected[xxml] def drp_=(port: Option[XPort]): Unit = { + _drp = port + } + + protected[xxml] def graphEdges(runtime: XMLCalabashRuntime): Unit = { + for (child <- allChildren) { + child match { + case pipe: XPipe => + pipe.graphEdges(runtime) + case _ => + throw XProcException.xiThisCantHappen("Port has a child that isn't a pipe?") + } + } + } + + override protected[xxml] def validate(): Unit = { + allChildren = validateExplicitConnections(_href, _pipe) + _href = None + _pipe = None + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + super.elaborateDefaultReadablePort(initial) + } + + override def toString: String = { + if (parent.isDefined) { + s"${parent.get} :: ${port}" + } else { + port + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XPortBindingContext.scala b/src/main/scala/com/xmlcalabash/model/xxml/XPortBindingContext.scala new file mode 100644 index 0000000..b513eac --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XPortBindingContext.scala @@ -0,0 +1,46 @@ +package com.xmlcalabash.model.xxml + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XPortBindingContext private(portMap: Map[String,XPort], internalPortMap: Map[String,XPort]) { + def this() = { + this(Map(), Map()) + } + + def withContainer(step: XContainer): XPortBindingContext = { + val newPorts = step.publicPipeConnections + val newInternalPorts = step.privatePipeConnections + new XPortBindingContext(portMap ++ newPorts, internalPortMap ++ newInternalPorts) + } + + def primaryPort(stepName: String): Option[XPort] = { + val prefix = s"${stepName}/" + for (port <- portMap.keySet) { + if (port.startsWith(prefix)) { + if (portMap(port).primary) { + return portMap.get(port) + } + } + } + None + } + + def port(pipe: XPipe): Option[XPort] = { + val key = s"${pipe.step.getOrElse("???")}/${pipe.port.getOrElse("???")}" + portMap.get(key) + } + + def privatePort(pipe: XPipe): Option[XPort] = { + val key = s"${pipe.step.getOrElse("???")}/${pipe.port.getOrElse("???")}" + if (portMap.contains(key)) { + portMap.get(key) + } else { + internalPortMap.get(key) + } + } + + def validate(pipe: XPipe): Boolean = { + port(pipe).isDefined + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XSelectFilter.scala b/src/main/scala/com/xmlcalabash/model/xxml/XSelectFilter.scala new file mode 100644 index 0000000..aae0c5a --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XSelectFilter.scala @@ -0,0 +1,23 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.params.{InlineLoaderParams, SelectFilterParams} +import com.xmlcalabash.runtime.{StepProxy, XMLCalabashRuntime} + +class XSelectFilter(parentStep: XArtifact, filterStep: XStep, input: XPort) extends XAtomicStep(filterStep.config, XProcConstants.cx_select_filter) { + staticContext = filterStep.staticContext + _synthetic = true + parent = parentStep + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val params = new SelectFilterParams(filterStep.staticContext, input.select.get, input.port, input.sequence) + + val start = parent.asInstanceOf[ContainerStart] + val impl = stepImplementation + impl.configure(config, stepType, name, Some(params)) + + val proxy = new StepProxy(runtime, stepType, impl, staticContext) + runtime.addNode(this, start.addAtomic(proxy, "cx:select-filter")) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XSkeletonStepSignature.scala b/src/main/scala/com/xmlcalabash/model/xxml/XSkeletonStepSignature.scala new file mode 100644 index 0000000..3035536 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XSkeletonStepSignature.scala @@ -0,0 +1,45 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import net.sf.saxon.s9api.QName + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XSkeletonStepSignature(val decl: XDeclareStep) { + private val _inputs = mutable.HashSet.empty[String] + private val _outputs = mutable.HashSet.empty[String] + private val _options = ListBuffer.empty[QName] + + private val typeAttr = decl.attributes.get(XProcConstants._type) + if (typeAttr.isEmpty) { + throw XProcException.xiThisCantHappen("Attempt to create skeleton step signature without type") + } + + private val _stepType = decl.staticContext.parseQName(typeAttr.get) + + for (child <- decl.allChildren) { + child match { + case _: XInput => + if (child.attributes.contains(XProcConstants._port)) { + _inputs += child.attributes(XProcConstants._port) + } + case _: XOutput => + if (child.attributes.contains(XProcConstants._port)) { + _outputs += child.attributes(XProcConstants._port) + } + case _: XOption => + if (child.attributes.contains(XProcConstants._name)) { + _options += decl.staticContext.parseQName(child.attributes(XProcConstants._name)) + } + case _ => + () + } + } + + def stepType: QName = _stepType + def inputs: Set[String] = _inputs.toSet + def outputs: Set[String] = _outputs.toSet + def options: List[QName] = _options.toList +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XStaticContext.scala b/src/main/scala/com/xmlcalabash/model/xxml/XStaticContext.scala new file mode 100644 index 0000000..d98360d --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XStaticContext.scala @@ -0,0 +1,86 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.Location +import com.jafpl.messages.Message +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.messages.{XdmNodeItemMessage, XdmValueItemMessage} +import com.xmlcalabash.util.{MinimalStaticContext, URIUtils, Urify, VoidLocation} +import net.sf.saxon.s9api.{QName, XdmValue} + +import java.net.URI +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XStaticContext() extends MinimalStaticContext() { + protected var _config: XMLCalabash = _ + protected var _baseURI: Option[URI] = None + protected var _location: Option[Location] = None + protected val _inscopeNamespaces: mutable.HashMap[String, String] = mutable.HashMap.empty[String, String] + protected var _inscopeConstants: ListBuffer[XNameBinding] = ListBuffer.empty[XNameBinding] + + def this(location: Option[Location], inscopeNamespaces: Map[String,String]) = { + this() + _location = location + _inscopeNamespaces ++= inscopeNamespaces + } + + def this(location: Location, inscopeNamespaces: Map[String,String]) = this(Some(location), inscopeNamespaces) + + def this(baseURI: URI, inscopeNamespaces: Map[String,String]) = { + this() + _baseURI = Some(baseURI) + _inscopeNamespaces ++= inscopeNamespaces + } + + def baseURI: Option[URI] = { + if (_baseURI.isDefined) { + _baseURI + } else { + if (location.isDefined && location.get.uri.isDefined) { + Some(new URI(Urify.urify(location.get.uri.get))) + } else { + Some(URIUtils.cwdAsURI) + } + } + } + protected[xxml] def baseURI_=(base: URI): Unit = { + _baseURI = Some(base) + } + + def location: Option[Location] = _location + protected[xxml] def location_=(loc: Location): Unit = { + _location = Some(loc) + } + + override def inscopeNamespaces: Map[String, String] = _inscopeNamespaces.toMap + + override def inscopeConstants: Map[QName, XNameBinding] = { + val map = mutable.HashMap.empty[QName, XNameBinding] + for (opt <- _inscopeConstants) { + map.put(opt.name, opt) + } + map.toMap + } + + def inscopeConstantBindings: Map[String, Message] = { + val bindings = mutable.HashMap.empty[String,Message] + for ((name,value) <- inscopeConstants) { + bindings.put(name.getClarkName, value.constantValue.get) + } + bindings.toMap + } + + def inscopeConstantValues: Map[QName, XdmValue] = { + val statics = mutable.HashMap.empty[QName,XdmValue] + for ((name, value) <- inscopeConstantBindings) { + val qname = parseClarkName(name) + value match { + case msg: XdmNodeItemMessage => + statics.put(qname,msg.item) + case msg: XdmValueItemMessage => + statics.put(qname,msg.item) + } + } + statics.toMap + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XStep.scala b/src/main/scala/com/xmlcalabash/model/xxml/XStep.scala new file mode 100644 index 0000000..69b90c9 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XStep.scala @@ -0,0 +1,351 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.Node +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.XMLCalabashRuntime +import com.xmlcalabash.util.MediaType +import net.sf.saxon.s9api.{QName, SaxonApiException} + +import scala.collection.mutable + +abstract class XStep(config: XMLCalabash) extends XArtifact(config) with XNamedArtifact with XGraphableArtifact { + private var _name = Option.empty[String] + private var _drp = Option.empty[XPort] + protected var _type: Option[QName] = None + protected[xxml] val dependsOn = mutable.HashMap.empty[String, Option[XStep]] + + def name: Option[String] = _name + + def stepName: String = _name.getOrElse(tumble_id) + + protected[xxml] def stepName_=(name: String): Unit = { + try { + _name = Some(staticContext.parseNCName(name)) + } catch { + case ex: Exception => + error(ex) + } + } + + protected[xxml] def drp: Option[XPort] = _drp + + protected[xxml] def drp_=(port: Option[XPort]): Unit = { + _drp = port + } + + def primaryOutput: Option[XPort] + + def outputs: Set[XOutput] = { + val decl = stepDeclaration + if (decl.isDefined) { + decl.get.children[XOutput].toSet + } else { + children[XOutput].toSet + } + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + + val pstep = if (synthetic) { + syntheticName.get.getNamespaceURI == XProcConstants.ns_p + } else { + staticContext.nodeName.getNamespaceURI == XProcConstants.ns_p + } + + for (name <- attributes.keySet) { + if (pstep && name.getNamespaceURI == XProcConstants.ns_p) { + error(XProcException.xsXProcNamespaceError(name, staticContext.nodeName, location)) + } + + name match { + case XProcConstants._name => + val aname = attr(XProcConstants._name) + try { + stepName = staticContext.parseNCName(aname.get) + } catch { + case _: SaxonApiException => + error(XProcException.xsBadTypeValue(aname.get, "xs:NCName", None)) + } + case XProcConstants.p_message => + if (!pstep) { + syntheticOption(XProcConstants.p_message, attr(name).get) + } + case XProcConstants._message => + if (pstep) { + syntheticOption(XProcConstants._message, attr(name).get) + } + case XProcConstants.p_depends => + if (!pstep) { + depends(attr(name).get) + } + case XProcConstants._depends => + if (pstep) { + staticContext.nodeName.getLocalName match { + case "when" => error(XProcException.xsBadAttribute(name, location)) + case "otherwise" => error(XProcException.xsBadAttribute(name, location)) + case "catch" => error(XProcException.xsBadAttribute(name, location)) + case "finally" => error(XProcException.xsBadAttribute(name, location)) + case _ => depends(attr(name).get) + } + } + case XProcConstants.p_expand_text => + if (!pstep) { + val value = attr(name).get + if (value != "true" && value != "false") { + error(XProcException.xsInvalidExpandText(name, value, location)) + } + } else { + error(XProcException.xsBadAttribute(name, None)) + } + case XProcConstants._expand_text => + if (pstep) { + val value = attr(name).get + if (value != "true" && value != "false") { + error(XProcException.xsInvalidExpandText(name, value, location)) + } + } else { + error(XProcException.xsBadAttribute(name, None)) + } + case XProcConstants.p_timeout => + if (!pstep) { + syntheticOption(name, attr(name).get) + } + case XProcConstants._timeout => + if (pstep) { + syntheticOption(name, attr(name).get) + } + case _ => + () + } + } + } + + protected def syntheticOption(name: QName, value: String): Unit = { + val option = new XWithOption(this, name, Some(value), None) + addChild(option) + } + + private def depends(value: String): Unit = { + if (value.trim == "") { + error(XProcException.xsBadTypeEmpty(value, location)) + return + } + + for (name <- value.trim.split("\\s+")) { + try { + staticContext.parseNCName(name) + dependsOn.put(name, None) + } catch { + case _: Exception => + error(XProcException.xsBadTypeValue(name, "xs:NCName", location)) + } + } + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + super.elaborateDefaultReadablePort(initial) + } + + override protected[xxml] def elaborateDependsConnections(inScopeSteps: Map[String, XStep]): Unit = { + for (name <- dependsOn.keySet) { + if (inScopeSteps.contains(name)) { + dependsOn.put(name, Some(inScopeSteps(name))) + } else { + error(XProcException.xsNotAStep(name, location)) + } + } + super.elaborateDependsConnections(inScopeSteps) + } + + def container: XContainer = { + val art = if (parent.isDefined) { + parent.get + } else { + this + } + + art match { + case decl: XContainer => + decl + case _ => + throw XProcException.xiThisCantHappen("Parent of step isn't a container?") + } + } + + def declarationContainer: XDeclContainer = { + var art = parent + while (art.isDefined) { + art.get match { + case decl: XDeclContainer => + return decl + case _ => () + } + art = art.get.parent + } + + if (art.isEmpty) { + this match { + case decl: XDeclContainer => + return decl + case _ => () + } + } + + throw XProcException.xiThisCantHappen("No ancestor of step is a declaration container?") + } + + protected def elaborateDynamicOptions(): Unit = { + children[XStep] foreach { _.elaborateDynamicOptions() } + } + + protected[xxml] def elaborateInsertSelectFilters(): Unit = { + if (parent.isDefined && parent.get.isInstanceOf[XContainer]) { + // If we're in a container, we could add filters + val container = parent.get.asInstanceOf[XContainer] + + for (child <- children[XPort]) { + val filterable = child match { + case _: XInput => true + case _: XWithInput => true + case _ => false + } + if (filterable && child.select.isDefined) { + addInputFilter(child, new XSelectFilter(container, this, child)) + } + } + } + + children[XStep] foreach { _.elaborateInsertSelectFilters() } + } + + protected[xxml] def elaborateInsertContentTypeFilters(): Unit = { + if (parent.isDefined && parent.get.isInstanceOf[XContainer]) { + // If we're in a container, we could add filters + val container = parent.get.asInstanceOf[XContainer] + + // We don't have to filter compound steps because they'll have + // filters for their inputs. + if (stepDeclaration.get.atomic) { + for (child <- children[XWithInput]) { + if (!MediaType.OCTET_STREAM.allowed(child.contentTypes)) { + var filter = false + for (pipe <- child.children[XPipe]) { + val from = pipe.from + if (from.isDefined) { + for (ctype <- pipe.from.get.contentTypes filter { _.inclusive }) { + filter = filter || !ctype.allowed(child.contentTypes) + } + } else { + throw XProcException.xiThisCantHappen("Pipe has unknown origin.") + } + } + + if (filter) { + addInputFilter(child, new XContentTypeChecker(container, child)) + } + } + } + } + } + + for (child <- children[XStep]) { + child.elaborateInsertContentTypeFilters() + } + } + + protected def addInputFilter(child: XPort, filter: XStep): Unit = { + val container = parent.get match { + case cont: XContainer => cont + case _ => + error(XProcException.xiThisCantHappen("Parent of filtering step isn't a container?")) + return + } + + val filterxwi = new XWithInput(filter, "source") + filterxwi.allChildren = child.allChildren + val filterxwo = new XWithOutput(filter, "result") + + val stepxwi = new XWithInput(this, child.port) + + insertBefore(stepxwi, child) + removeChild(child) + + stepxwi.validate() + + filter.addChild(filterxwi) + + if (child.selectBindings.nonEmpty) { + filter.addChild(child.selectBindings.get) + } + + filter.addChild(filterxwo) + + val pipe = new XPipe(stepxwi, filterxwo) + stepxwi.addChild(pipe) + + container.insertBefore(filter, this) + filterxwi.validate() + filterxwo.validate() + } + + def withinTryCatch: Boolean = { + var par: Option[XArtifact] = Some(this) + while (par.isDefined) { + par.get match { + case _: XTry => + return true + case _ => + () + } + par = par.get.parent + } + false + } + + def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + for (child <- allChildren) { + child match { + case graphable: XGraphableArtifact => + graphable.graphNodes(runtime, parent) + case _ => () + } + } + } + + protected def graphEdges(runtime: XMLCalabashRuntime): Unit = { + for (child <- allChildren) { + child match { + case step: XStep => + step.graphEdges(runtime) + case input: XInput => + input.graphEdges(runtime) + case input: XWithInput => + input.graphEdges(runtime) + case output: XOutput => + output.graphEdges(runtime) + case nb: XNameBinding => + nb.graphEdges(runtime) + case _ => + () + } + } + + for (name <- dependsOn.keySet) { + val step = dependsOn(name).get + val node = runtime.node(this) + runtime.graph.addOrderedEdge(runtime.node(step), "#depends_from", node, "#depends_to") + } + } + + override def toString: String = { + if (stepName != tumble_id) { + s"${staticContext.nodeName}(${stepName};${tumble_id})" + } else { + s"${staticContext.nodeName}(${stepName})" + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XTry.scala b/src/main/scala/com/xmlcalabash/model/xxml/XTry.scala new file mode 100644 index 0000000..ea647bc --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XTry.scala @@ -0,0 +1,191 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.XMLCalabashRuntime +import com.xmlcalabash.util.MediaType +import net.sf.saxon.s9api.QName + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XTry(config: XMLCalabash) extends XContainer(config) { + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + _drp = initial + var curdrp = initial + for (child <- allChildren) { + child match { + case _: XTryCatchBranch => + curdrp = initial + child.elaborateDefaultReadablePort(curdrp) + case _ => + curdrp = child.elaborateDefaultReadablePort(curdrp) + } + } + + children[XOutput] find { _.primary } + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + wrapTryInGroup() + + val codeList = mutable.Set.empty[QName] + var seenCatch = false + var seenCatchWithoutCode = false + var seenFinally = false + var seenPipeline = false + + //val newScope = checkStepNameScoping(inScopeNames) + val newChildren = ListBuffer.empty[XArtifact] + for (child <- allChildren) { + child.validate() + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case xcatch: XCatch => + if (seenFinally) { + error(XProcException.xsInvalidTryCatch("In a p:try, p:finally must be last", location)) + } + seenCatch = true + if (seenCatchWithoutCode) { + error(XProcException.xsCatchMissingCode(location)) + } + if (xcatch.codes.isEmpty) { + seenCatchWithoutCode = true + } else { + for (code <- xcatch.codes) { + if (codeList.contains(code)) { + error(XProcException.xsCatchBadCode(code, location)) + } else { + codeList += code + } + } + } + newChildren += xcatch + case xfinally: XFinally => + if (seenFinally) { + error(XProcException.xsInvalidTryCatch("In a p:try, there can be at most one p:finally", location)) + } + seenFinally = true + newChildren += xfinally + case step: XGroup => + if (seenCatch || seenFinally) { + error(XProcException.xsInvalidTryCatch("In a p:try, no steps may follow p:catch or p:finally", location)) + } else { + seenPipeline = true + newChildren += step + } + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + if ((!seenCatch && !seenFinally) || !seenPipeline) { + error(XProcException.xsInvalidTryCatch("Catch or finally and pipeline required", None)) + return + } + + allChildren = newChildren.toList + + val outports = mutable.HashMap.empty[String, Option[Boolean]] + + for (branch <- children[XContainer]) { + for (output <- branch.children[XOutput]) { + if (outports.contains(output.port)) { + val p1 = outports(output.port) + val p2 = output + if ((p1.isDefined && !p2.primarySpecified) || (p1.isEmpty && p2.primarySpecified)) { + // FIXME: setup default for primary on p:output + error(XProcException.xiUserError("Catch branches with different primary ports")) + } else if (p1.isDefined) { + if (p1.get != p2.primary) { + error(XProcException.xiUserError("Catch branch with different primacy")) + } + } + } else { + if (output.primarySpecified) { + outports.put(output.port, Some(output.primary)) + } else { + outports.put(output.port, None) + } + } + } + } + + for (port <- outports.keySet) { + val oport = if (port == "") { + None + } else { + Some(port) + } + val output = new XOutput(this, oport) + output.primary = outports(port).getOrElse(false) + output.sequence = true + output.contentTypes = MediaType.MATCH_ANY + insertBefore(output, allChildren.head) + + for (branch <- children[XContainer]) { + val out = branch.children[XOutput] find { _.port == port } + if (out.isDefined) { + val pipe = new XPipe(output, branch.stepName, port) + output.addChild(pipe) + } + } + } + } + + private def wrapTryInGroup(): Unit = { + val tryElements = ListBuffer.empty[XArtifact] + var firstCatch = Option.empty[XTryCatchBranch] + + for (child <- allChildren) { + if (firstCatch.isEmpty) { + child match { + case tc: XTryCatchBranch => + firstCatch = Some(tc) + case _ => + tryElements += child + } + } + } + + if (tryElements.isEmpty) { + throw XProcException.xsInvalidTryCatch("A p:try must have at least one step", location) + } + + if (tryElements.length == 1 && tryElements.head.isInstanceOf[XGroup]) { + return + } + + val group = new XGroup(this) + group.allChildren = tryElements.toList + + val newChildren = ListBuffer.empty[XArtifact] + newChildren += group + + var found = false + for (child <- allChildren) { + if (!found && firstCatch.isDefined) { + found = firstCatch.get eq child + } + if (found) { + newChildren += child + } + } + + allChildren = newChildren.toList + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[ContainerStart] + val node = start.addTryCatch(stepName, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XTryCatchBranch.scala b/src/main/scala/com/xmlcalabash/model/xxml/XTryCatchBranch.scala new file mode 100644 index 0000000..427afb1 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XTryCatchBranch.scala @@ -0,0 +1,67 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.SaxonTreeBuilder +import com.xmlcalabash.runtime.XProcXPathExpression + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +abstract class XTryCatchBranch(config: XMLCalabash) extends XContainer(config) { + + protected def orderChildren(): Unit = { + // nop + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + var seenPipeline = false + + //val newScope = checkStepNameScoping(inScopeNames) + for (child <- allChildren) { + child.validate() + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XOutput => + if (seenPipeline) { + error(XProcException.xiUserError("output can't follow steps")) + } + case _: XVariable => + seenPipeline = true + case _: XStep => + seenPipeline = true + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + if (children[XOutput].length == 1) { + val output = children[XOutput].head + if (!output.primarySpecified) { + output.primary = true + } + } + + val xi = new XInput(this, Some("error")) + xi.primary = true + xi.sequence = true + if (allChildren.isEmpty) { + addChild(xi) + } else { + insertBefore(xi, allChildren.head) + } + + constructDefaultOutput() + + orderChildren() + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("name", Some(stepName)) + dumpTree(sb, nodeName.toString, attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XVariable.scala b/src/main/scala/com/xmlcalabash/model/xxml/XVariable.scala new file mode 100644 index 0000000..8a50ef0 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XVariable.scala @@ -0,0 +1,127 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.jafpl.messages.Message +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants, XValueParser} +import com.xmlcalabash.runtime.params.XPathBindingParams +import com.xmlcalabash.runtime.{StepProxy, XMLCalabashRuntime, XProcVtExpression, XProcXPathExpression} +import com.xmlcalabash.steps.internal.ValueComputation +import com.xmlcalabash.util.{MediaType, TypeUtils} +import net.sf.saxon.s9api.XdmAtomicValue + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XVariable(config: XMLCalabash) extends XNameBinding(config) with XGraphableArtifact { + private var _allowedValues = Option.empty[List[XdmAtomicValue]] + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + + if (_select.isEmpty) { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._select, location)) + } + + _allowedValues = checkValueTokens + _href = attr(XProcConstants._href) + _pipe = attr(XProcConstants._pipe) + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + val inputs = validateExplicitConnections(_href, _pipe) + _href = None + _pipe = None + val newChildren = ListBuffer.empty[XArtifact] + + if (inputs.nonEmpty) { + val xwi = new XWithInput(this, "source") + xwi.primary = true + xwi.sequence = false + xwi.contentTypes = MediaType.MATCH_ANY + newChildren += xwi + xwi.allChildren = inputs + } + + val xwo = new XWithOutput(this, "result") + xwo.primary = true + xwo.sequence = true + xwo.contentTypes = MediaType.MATCH_ANY + newChildren += xwo + + allChildren = newChildren.toList + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + val bcontext = super.elaborateNameBindings(initial) + + var newContext = bcontext + try { + newContext = bcontext.withBinding(this) + } catch { + case ex: Exception => + error(ex) + } + + newContext + } + + override protected def promotedStaticValue(staticValueMsg: XdmValueItemMessage): XdmValueItemMessage = { + XNameBinding.promotedValue(config, name, declaredType, None, staticValueMsg) + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + if (constantValue.isDefined || !usedByPipeline) { + return + } + + val start = parent.asInstanceOf[ContainerStart] + val params = new XPathBindingParams(collection) + val init = new XProcXPathExpression(staticContext, _select.getOrElse("()"), _declaredType, _allowedValues, params) + // FIXME: does params here have to include all the statics? + runtime.addNode(this, start.addOption(_name.getClarkName, init, params)) + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + if (Option(_name).isDefined) { + attr.put("name", Some(_name.getEQName)) + } + + if (constantValue.isDefined) { + attr.put("constant-value", Some(constantValue.get.item.toString)) + } else { + attr.put("select", _select) + attr.put("avt", _avt) + } + + attr.put("as", as) + attr.put("required", _required) + attr.put("visiblity", _visibility) + + if (drp.isDefined) { + attr.put("drp", Some(drp.get.tumble_id)) + } + + dumpTree(sb, "p:variable", attr.toMap) + } + + override def toString: String = { + if (constantValue.isDefined) { + s"${name}: ${constantValue.get.item} (constant)" + } else if (_avt.isDefined) { + s"${name}: ${_avt.get} (avt)" + } else { + if (_select.isDefined) { + s"${name}: ${_select.get} (select)" + } else { + s"${name}: ???" + } + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XViewport.scala b/src/main/scala/com/xmlcalabash/model/xxml/XViewport.scala new file mode 100644 index 0000000..bdc6976 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XViewport.scala @@ -0,0 +1,41 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.model.util.{XMLViewportComposer, XProcConstants} +import com.xmlcalabash.runtime.XMLCalabashRuntime + +class XViewport(config: XMLCalabash) extends XLoopingStep(config) { + private var _match: String = _ + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + + if (attributes.contains(XProcConstants._match)) { + _match = attr(XProcConstants._match).get + } else { + throw new RuntimeException("Viewport must have match") + } + } + + override protected[xxml] def validate(): Unit = { + super.validate() + + var found = false + for (child <- children[XOutput]) { + if (found) { + throw new RuntimeException("Viewport must not have more than one output") + } + found = true + child.port = "result" + } + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val start = parent.asInstanceOf[ContainerStart] + val composer = new XMLViewportComposer(config, staticContext, _match) + val node = start.addViewport(composer, stepName, containerManifold) + runtime.addNode(this, node) + super.graphNodes(runtime, node) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XWhen.scala b/src/main/scala/com/xmlcalabash/model/xxml/XWhen.scala new file mode 100644 index 0000000..ee00d32 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XWhen.scala @@ -0,0 +1,209 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.messages.Message +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.model.util.{XProcConstants, XValueParser} +import com.xmlcalabash.runtime.{XProcVtExpression, XProcXPathExpression} +import net.sf.saxon.s9api.QName + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XWhen(config: XMLCalabash) extends XChooseBranch(config) { + private val _namebindings = ListBuffer.empty[XNameBinding] + private var _constantValue = Option.empty[XdmValueItemMessage] + private var _contextDependent = false + private var _wasIf = false + + def this(choose: XChoose, test: String, collection: Boolean) = { + this(choose.config) + staticContext = choose.staticContext + parent = choose + synthetic = true + syntheticName = XProcConstants.p_when + _test = test + _collection = collection + } + + def this(choose: XChoose, test: String, collection: Boolean, wasIf: Boolean) = { + this(choose, test, collection) + _wasIf = wasIf + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + val coll = attr(XProcConstants._collection) + if (coll.isDefined) { + coll.get match { + case "true" => _collection = true + case "false" => _collection = false + case _ => + error(XProcException.xsBadTypeValue(coll.get, "xs:boolean", None)) + } + } + + if (attributes.contains(XProcConstants._test)) { + _test = attr(XProcConstants._test).get + } else { + error(XProcException.xsMissingRequiredAttribute(XProcConstants._test, None)) + } + } + + override def validate(): Unit = { + if (synthetic) { + // There are no attributes and we've already validated the children + return + } + checkAttributes() + checkEmptyAttributes() + + var seenWithInput = false + var seenPipeline = false + + //val newScope = checkStepNameScoping(inScopeNames) + for (child <- allChildren) { + child.validate() + child match { + case _: XPipeinfo => () + case _: XDocumentation => () + case _: XWithInput => + if (seenWithInput) { + error(XProcException.xiUserError("More than one p:with-input in choose")) + } + if (seenPipeline) { + error(XProcException.xiUserError("with-input can't follow steps")) + } + seenWithInput = true + case _: XOutput => + if (seenPipeline) { + error(XProcException.xiUserError("output can't follow steps")) + } + case _: XVariable => + seenPipeline = true + case _: XStep => + seenPipeline = true + case _ => + error(XProcException.xsElementNotAllowed(child.nodeName, None)) + } + } + + if (children[XOutput].length == 1) { + val output = children[XOutput].head + if (!output.primarySpecified) { + output.primary = true + } + } + + constructDefaultOutput() + + if (_wasIf) { + val primary = children[XOutput] find { _.primary == true } + if (primary.isEmpty) { + error(XProcException.xsPrimaryOutputRequired(location)) + } + } + + orderChildren() + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + try { + val refs = mutable.HashSet.empty[QName] + + val parser = new XValueParser(config, staticContext, _test) + refs ++= parser.variables + _contextDependent = parser.contextDependent + + var constant = parser.static + for (ref <- refs) { + val cbind = initial.inScopeConstants.get(ref) + val dbind = initial.inScopeDynamics.get(ref) + if (cbind.isDefined) { + // ok + } else if (dbind.isDefined) { + constant = false + dbind.get match { + case v: XVariable => + _namebindings += v + case opt: XOption => + _namebindings += opt + case _ => + error(XProcException.xiThisCantHappen(s"Unexpected name binding: ${dbind.get}")) + } + } else { + error(XProcException.xsNoBindingInExpression(ref, None)) + } + } + + if (exceptions.nonEmpty) { + return initial + } + + if (constant) { + drp = None // If it's constant, there's no need for an input + + val expr = new XProcXPathExpression(staticContext, _test) + val bindings = mutable.HashMap.empty[String,Message] + for ((name,value) <- initial.inScopeConstants) { + bindings.put(name.getClarkName, value.constantValue.get) + } + + try { + val constantVal = config.expressionEvaluator.value(expr, List(), bindings.toMap, None) + _constantValue = Some(constantVal) + } catch { + case ex: Exception => + if (withinTryCatch) { + // nevermind, just let it go bang later + } else { + throw ex + } + } + } + } catch { + case ex: Exception => + error(ex) + } + + super.elaborateNameBindings(initial) + } + + override def elaboratePortConnections(): Unit = { + val input = children[XWithInput].headOption + if (input.isDefined) { + for (binding <- _namebindings) { + input.get.addChild(new XNamePipe(binding)) + } + } else { + if (_contextDependent || _namebindings.nonEmpty) { + val newinput = new XWithInput(parent.get, "condition") + newinput.staticContext = staticContext + newinput.parent = this + + if (parent.get.children[XWithInput].nonEmpty) { + for (ds <- parent.get.children[XWithInput].head.children[XDataSource]) { + ds match { + case pipe: XPipe => + val newpipe = new XPipe(pipe) + newinput.addChild(newpipe) + case _ => + error(XProcException.xiThisCantHappen("Choose with-input is not a pipe?")) + } + } + } + + for (binding <- _namebindings) { + val pipe = new XPipe(newinput, binding.tumble_id, "result") + newinput.addChild(pipe) + } + + insertBefore(newinput, allChildren.head) + } + } + + super.elaboratePortConnections() + } + +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XWithInput.scala b/src/main/scala/com/xmlcalabash/model/xxml/XWithInput.scala new file mode 100644 index 0000000..d6b2597 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XWithInput.scala @@ -0,0 +1,246 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants, XValueParser} +import com.xmlcalabash.util.MediaType + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XWithInput(config: XMLCalabash) extends XPort(config) { + + def this(parentStep: XArtifact, port: String) = { + this(parentStep.config) + parent = parentStep + _synthetic = true + _syntheticName = Some(XProcConstants.p_with_input) + _port = port + _primary = Some(true) + _sequence = Some(true) + _content_types = MediaType.MATCH_ANY + staticContext = parentStep.staticContext + } + + def this(parentStep: XArtifact, port: String, primary: Boolean, sequence: Boolean, contentTypes: List[MediaType]) = { + this(parentStep.config) + parent = parentStep + _synthetic = true + _syntheticName = Some(XProcConstants.p_with_input) + _port = port + _primary = Some(primary) + _sequence = Some(sequence) + _content_types = contentTypes + staticContext = parentStep.staticContext + } + + override protected[xxml] def port_=(port: String): Unit = { + if (portSpecified) { + throw XProcException.xiThisCantHappen("Attempt to change port on p:input") + } + _synthetic = true + _syntheticName = Some(XProcConstants.p_with_input) + _port = port + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + try { + if (attributes.contains(XProcConstants._port)) { + _port = staticContext.parseNCName(attr(XProcConstants._port).get) + } + } catch { + case ex: XProcException => + error(ex) + } + + if (_port != "#anon" && parent.isDefined) { + parent.get match { + case c: XForEach => + // FIXME: it's a weird sharp edge that the port has to be called 'source' in the graph + if (_port != "source") { + error(XProcException.xsPortNotAllowed(_port, c.name.getOrElse(c.tumble_id), location)) + } + case c: XViewport => + error(XProcException.xsPortNotAllowed(_port, c.name.getOrElse(c.tumble_id), location)) + case c: XChoose => + error(XProcException.xsPortNotAllowed(_port, c.name.getOrElse(c.tumble_id), location)) + case c: XWhen => + error(XProcException.xsPortNotAllowed(_port, c.name.getOrElse(c.tumble_id), location)) + case c: XIf => + error(XProcException.xsPortNotAllowed(_port, c.name.getOrElse(c.tumble_id), location)) + case _ => + () + } + } + + _select = attr(XProcConstants._select) + _href = attr(XProcConstants._href) + _pipe = attr(XProcConstants._pipe) + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + val decl = stepDeclaration + if (decl.isDefined) { + if (decl.get.inputPorts.contains(port)) { + val dinput = decl.get.input(port) + primary = dinput.primary + sequence = dinput.sequence + contentTypes = dinput.contentTypes + } + } else { + parent.get match { + case _: XWhen => + _port = "condition" + case _: XForEach => + primary = false + sequence = true + contentTypes = MediaType.MATCH_ANY + case _ => + () + } + } + + super.validate() + } + + override protected[xxml] def elaborateDefaultReadablePort(initial: Option[XPort]): Option[XPort] = { + super.elaborateDefaultReadablePort(initial) + if (!primary) { + // If the input isn't primary, it doesn't get automatically joined to the DRP + // Except for looping steps where the "current" port is primary for the inner steps + // even though this port is primary for the loop itself. Sigh. + parent.get match { + case _: XLoopingStep => + () + case _ => + drp = None + } + } + initial + } + + override protected[xxml] def elaborateNameBindings(initial: XNameBindingContext): XNameBindingContext = { + super.elaborateNameBindings(initial); + + if (_select.isEmpty) { + return initial + } + + try { + val parser = new XValueParser(config, staticContext, _select.get) + + val refs = parser.variables + var static = parser.static + + val namepipe = ListBuffer.empty[XNameBinding] + for (ref <- refs) { + val cbind = initial.inScopeConstants.get(ref) + val dbind = initial.inScopeDynamics.get(ref) + if (cbind.isDefined) { + // ok + } else if (dbind.isDefined) { + static = false + dbind.get match { + case v: XVariable => + namepipe += v + case o: XOption => + namepipe += o + case _ => + error(XProcException.xiThisCantHappen(s"Unexpected name binding: ${dbind.get}")) + } + } else { + error(XProcException.xsNoBindingInExpression(ref, None)) + } + } + + if (exceptions.nonEmpty) { + return initial + } + + if (static) { + throw XProcException.xiThisCantHappen("Static binding in with-input unsupported") + } else { + if (namepipe.nonEmpty) { + val xwi = new XWithInput(this, "#bindings", false, true, MediaType.MATCH_ANY) + for (binding <- namepipe) { + val xstep = initial.inScopeDynamics.get(binding.name) + val pipe = new XPipe(xwi, xstep.get.tumble_id, "result") + xwi.addChild(pipe) + } + _selectBindings = Some(xwi) + } + } + } catch { + case ex: Exception => + error(ex) + } + + initial + } + + override protected[xxml] def elaboratePortConnections(): Unit = { + if (drp.isDefined) { + if (children[XDataSource].isEmpty && port != "#bindings") { + val pipe = new XPipe(drp.get) + addChild(pipe) + } + } else { + if (children[XDataSource].isEmpty && primary) { + if (parent.isDefined) { + parent.get match { + case _: XWhen => + // Can't raise this statically in case it doesn't actually arise + logger.debug("The p:when expression will fail dynamically because no context item is available") + case step: XAtomicStep => + val decl = step.stepDeclaration.get + val input = decl.input(port) + if (input.defaultInputs.nonEmpty) { + for (ds <- input.defaultInputs) { + ds match { + case inline: XInline => + inline.staticContext = inline.staticContext.withConstants(input.staticContext.constants) + addChild(new XInline(this, inline)) + case doc: XDocument => + doc.staticContext = doc.staticContext.withConstants(input.staticContext.constants) + addChild(new XDocument(this, doc)) + case _: XEmpty => + addChild(new XEmpty(this)) + case _ => + throw XProcException.xiThisCantHappen(s"Default inputs contain ${ds}") + } + } + } else { + throw XProcException.xsUnconnectedPrimaryInputPort(step.name.getOrElse(step.tumble_id), port, location) + } + case step: XStep => + throw XProcException.xsUnconnectedPrimaryInputPort(step.name.getOrElse(step.tumble_id), port, location) + case _ => + throw XProcException.xiThisCantHappen("Parent of p:with-input is not a step?") + } + } else { + throw XProcException.xiThisCantHappen("A p:with-input has no parent?") + } + } + } + + super.elaboratePortConnections() + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("port", Some(_port)) + attr.put("select", _select) + attr.put("primary", _primary) + attr.put("sequence", _sequence) + + if (drp.isDefined) { + attr.put("drp", Some(drp.get.tumble_id)) + } + + dumpTree(sb, "p:with-input", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XWithOption.scala b/src/main/scala/com/xmlcalabash/model/xxml/XWithOption.scala new file mode 100644 index 0000000..0070535 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XWithOption.scala @@ -0,0 +1,183 @@ +package com.xmlcalabash.model.xxml + +import com.jafpl.messages.Message +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants, XValueParser} +import com.xmlcalabash.runtime.{XProcVtExpression, XProcXPathExpression} +import com.xmlcalabash.steps.internal.ValueComputation +import com.xmlcalabash.util.{S9Api, TypeUtils} +import net.sf.saxon.ma.arrays.ArrayItemType +import net.sf.saxon.ma.map.{MapItem, MapType} +import net.sf.saxon.s9api.{QName, XdmMap} + +import java.net.URISyntaxException +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XWithOption(config: XMLCalabash) extends XNameBinding(config) { + protected[xxml] def this(parent: XArtifact, name: QName, avt: Option[String], select: Option[String]) = { + this(parent.config) + + if ((avt.isDefined && select.isDefined) || (avt.isEmpty && select.isEmpty)) { + throw XProcException.xiThisCantHappen("Exactly one of avt or select must be defined on XWithOption") + } + + this.parent = parent + staticContext = parent.staticContext + _synthetic = true + _name = name + _avt = avt + _select = select + } + + override protected[xxml] def checkAttributes(): Unit = { + super.checkAttributes() + _href = attr(XProcConstants._href) + _pipe = attr(XProcConstants._pipe) + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + val stepdecl = stepDeclaration + if (stepdecl.isDefined) { + val optdecl = stepdecl.get.option(name) + if (optdecl.isEmpty) { + error(XProcException.xsUndeclaredOption(stepdecl.get.stepType.get, name, location)) + } else if (optdecl.get.static) { + error(XProcException.xsRedeclareStatic(name, location)) + } + } + + // Any option shortcut will have been marked as an AVT. + // But if the actual option type is a map or array, that's not really an AVT! + if (_avt.isDefined) { + if (stepdecl.isDefined) { + val optdecl = stepdecl.get.option(name).get + if (optdecl.declaredType.isDefined) { + val opttype = optdecl.declaredType.get.getItemType + opttype.getUnderlyingItemType match { + case _: MapType => + _select = _avt + _avt = None + _qnameKeys = optdecl.qnameKeys + case _: ArrayItemType => + _select = _avt + _avt = None + case _ => + () + } + } + } + } + + allChildren = validateExplicitConnections(_href, _pipe) + _href = None + _pipe = None + } + + protected[xxml] def elaborateDynamicOptions(): Unit = { + if (constantValue.isDefined) { + return + } + + val container = ancestorContainer.get + val compute = if (_avt.isDefined) { + new ValueComputation(container, name, XValueParser.parseAvt(_avt.get), collection) + } else { + new ValueComputation(container, name, _select.get, collection) + } + + compute.dependsOn ++= parent.get.asInstanceOf[XStep].dependsOn + + _computeValue = Some(compute) + + val xwi_source = new XWithInput(compute, "source") + val xwi_bindings = new XWithInput(compute, "#bindings") + val optchildren = ListBuffer.empty[XArtifact] + for (child <- allChildren) { + child match { + case xi: XWithInput => + optchildren ++= xi.allChildren + case _ => + optchildren += child + } + } + allChildren = List() + + for (child <- optchildren) { + child match { + case nb: XNamePipe => + val pipe = new XPipe(this, nb.binding.tumble_id, "result") + xwi_bindings.addChild(pipe) + case pipe: XPipe => + xwi_source.addChild(pipe) + case _ => + throw XProcException.xiThisCantHappen(s"Children of p:with-option include ${child}?") + } + } + + if (xwi_source.allChildren.nonEmpty) { + compute.addChild(xwi_source) + } + + if (xwi_bindings.allChildren.nonEmpty) { + compute.addChild(xwi_bindings) + } + + val xwo = new XWithOutput(compute, "result") + compute.addChild(xwo) + + var bwi = parent.get.children[XWithInput] find { _.port == "#bindings" } + if (bwi.isEmpty) { + bwi = Some(new XWithInput(this, "#bindings")) + addChild(bwi.get) + } + bwi.get.addChild(new XPipe(bwi.get, compute.stepName, "result")) + + container.insertBefore(compute, parent.get) + + xwi_source.validate() + xwi_bindings.validate() + xwo.validate() + } + + // ======================================================================================= + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + if (Option(_name).isDefined) { + attr.put("name", Some(_name.getEQName)) + } + + if (constantValue.isDefined) { + attr.put("constant-value", Some(constantValue.get.item.toString)) + } else { + attr.put("select", _select) + attr.put("avt", _avt) + } + + if (drp.isDefined) { + attr.put("drp", Some(drp.get.tumble_id)) + } + + dumpTree(sb, "p:with-option", attr.toMap) + } + + override def toString: String = { + if (constantValue.isDefined) { + s"${name}: ${constantValue.get.item} (constant)" + } else if (_avt.isDefined) { + s"${name}: ${_avt.get} (avt)" + } else { + if (_select.isDefined) { + s"${name}: ${_select.get} (select)" + } else { + s"${name}: ???" + } + } + } +} diff --git a/src/main/scala/com/xmlcalabash/model/xxml/XWithOutput.scala b/src/main/scala/com/xmlcalabash/model/xxml/XWithOutput.scala new file mode 100644 index 0000000..6dcce59 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/model/xxml/XWithOutput.scala @@ -0,0 +1,68 @@ +package com.xmlcalabash.model.xxml + +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.util.MediaType + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +class XWithOutput(parart: XArtifact, port: String) extends XPort(parart.config) { + private val _readBy = ListBuffer.empty[XArtifact] + + _synthetic = true + staticContext = parart.staticContext + this.parent = parart + _port = port + + protected[xxml] def readBy: List[XArtifact] = _readBy.toList + protected[xxml] def readBy_=(art: XArtifact): Unit = { + _readBy += art + } + + override protected[xxml] def checkAttributes(): Unit = { + // it's synthetic + } + + override protected[xxml] def validate(): Unit = { + checkAttributes() + checkEmptyAttributes() + + val decl = stepDeclaration + + if (decl.isEmpty) { + parent.get match { + case atomic: XAtomicStep => + if (atomic.stepType == XProcConstants.cx_document_loader + || atomic.stepType == XProcConstants.cx_inline_loader) { + _primary = Some(true) + _sequence = Some(false) + _content_types = MediaType.MATCH_ANY.toList + } else { + error(XProcException.xiThisCantHappen("Parent of with-output isn't a cx: loader?")) + } + case _ => + error(XProcException.xiThisCantHappen("Grandparent of with-output isn't an atomic step?")) + } + + return + } + + if (decl.isDefined && decl.get.outputPorts.contains(port)) { + val doutput = decl.get.output(port) + primary = doutput.primary + sequence = doutput.sequence + contentTypes = doutput.contentTypes + } + + super.validate() + } + + override def dumpTree(sb: SaxonTreeBuilder): Unit = { + val attr = mutable.HashMap.empty[String, Option[Any]] + attr.put("port", Some(_port)) + attr.put("primary", _primary) + attr.put("sequence", _sequence) + dumpTree(sb, "p:with-output", attr.toMap) + } +} diff --git a/src/main/scala/com/xmlcalabash/runtime/BufferingConsumer.scala b/src/main/scala/com/xmlcalabash/runtime/BufferingConsumer.scala index 9b0c52c..d3e97dc 100644 --- a/src/main/scala/com/xmlcalabash/runtime/BufferingConsumer.scala +++ b/src/main/scala/com/xmlcalabash/runtime/BufferingConsumer.scala @@ -4,11 +4,11 @@ import com.jafpl.messages.Message import com.jafpl.steps.DataConsumer import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.XProcItemMessage -import com.xmlcalabash.model.xml.DeclareOutput +import com.xmlcalabash.model.xxml.XOutput import scala.collection.mutable.ListBuffer -class BufferingConsumer(output: DeclareOutput) extends DataConsumer { +class BufferingConsumer(output: XOutput) extends DataConsumer { private val _items = ListBuffer.empty[XProcItemMessage] def messages: List[XProcItemMessage] = _items.toList diff --git a/src/main/scala/com/xmlcalabash/runtime/DynamicContext.scala b/src/main/scala/com/xmlcalabash/runtime/DynamicContext.scala index 07d595b..096db02 100644 --- a/src/main/scala/com/xmlcalabash/runtime/DynamicContext.scala +++ b/src/main/scala/com/xmlcalabash/runtime/DynamicContext.scala @@ -1,13 +1,13 @@ package com.xmlcalabash.runtime -import java.net.URI import com.jafpl.graph.{Location, LoopStart} import com.jafpl.messages.Message -import com.xmlcalabash.model.xml.{Artifact, ForEach, ForLoop, ForUntil} +import com.xmlcalabash.model.xxml.{XArtifact, XStep} import net.sf.saxon.om.Item import net.sf.saxon.s9api.{QName, XdmNode, XdmValue} import net.sf.saxon.value.StringValue +import java.net.URI import scala.collection.mutable import scala.util.DynamicVariable @@ -18,7 +18,7 @@ object DynamicContext { } class DynamicContext() { - private var _artifact = Option.empty[Artifact] + private var _artifact = Option.empty[XArtifact] private var _iterationPosition = 1L private var _iterationSize = 1L private val _documents = mutable.HashMap.empty[Any,Message] @@ -31,27 +31,33 @@ class DynamicContext() { private var _injId = Option.empty[String] private var _injType = Option.empty[QName] - def this(artifact: Option[Artifact]) = { + def this(artifact: XArtifact) = { this() + _artifact = Some(artifact) + } + + def this(runtime: XMLCalabashRuntime, artifact: XArtifact) = { + this(artifact) - _artifact = artifact var found = false - var p: Option[Artifact] = artifact + var p: Option[XArtifact] = Some(artifact) while (!found && p.isDefined) { - if (p.get.graphNode.isDefined) { - p.get.graphNode.get match { - case node: LoopStart => - found = true - _iterationPosition = node.iterationPosition - _iterationSize = node.iterationSize - case _ => () - } + p.get match { + case step: XStep => + runtime.node(step) match { + case node: LoopStart => + found = true + _iterationPosition = node.iterationPosition + _iterationSize = node.iterationSize + case _ => () + } + case _ => () } p = p.get.parent } } - def artifact: Option[Artifact] = _artifact + def artifact: Option[XArtifact] = _artifact def iterationPosition: Long = _iterationPosition def iterationSize: Long = _iterationSize diff --git a/src/main/scala/com/xmlcalabash/runtime/NameValueBinding.scala b/src/main/scala/com/xmlcalabash/runtime/NameValueBinding.scala index b98b0e2..b3f442b 100644 --- a/src/main/scala/com/xmlcalabash/runtime/NameValueBinding.scala +++ b/src/main/scala/com/xmlcalabash/runtime/NameValueBinding.scala @@ -1,9 +1,10 @@ package com.xmlcalabash.runtime import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{QName, XdmValue} -class NameValueBinding(val name: QName, val value: XdmValue, val meta: XProcMetadata, val context: StaticContext) { +class NameValueBinding(val name: QName, val value: XdmValue, val meta: XProcMetadata, val context: MinimalStaticContext) { def this(name: QName, message: XdmValueItemMessage) = this(name, message.item, message.metadata, message.context) def this(name: QName, value: XdmValue, message: XdmValueItemMessage) = diff --git a/src/main/scala/com/xmlcalabash/runtime/PrintingConsumer.scala b/src/main/scala/com/xmlcalabash/runtime/PrintingConsumer.scala index 04e6007..23d6c43 100644 --- a/src/main/scala/com/xmlcalabash/runtime/PrintingConsumer.scala +++ b/src/main/scala/com/xmlcalabash/runtime/PrintingConsumer.scala @@ -7,26 +7,26 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{AnyItemMessage, XProcItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.UniqueId -import com.xmlcalabash.model.xml.DeclareOutput +import com.xmlcalabash.model.xxml.XOutput import com.xmlcalabash.util.S9Api import net.sf.saxon.s9api.{QName, Serializer} import org.slf4j.{Logger, LoggerFactory} -class PrintingConsumer private(config: XMLCalabashRuntime, output: DeclareOutput, outputs: Option[List[String]]) extends DataConsumer { +class PrintingConsumer private(config: XMLCalabashRuntime, output: XOutput, outputs: Option[List[String]]) extends DataConsumer { protected val logger: Logger = LoggerFactory.getLogger(this.getClass) private val _id = UniqueId.nextId.toString private var index = 0 - def this(config: XMLCalabashRuntime, output: DeclareOutput) = { + def this(config: XMLCalabashRuntime, output: XOutput) = { this(config, output, None) } - def this(config: XMLCalabashRuntime, output: DeclareOutput, outputs: List[String]) = { + def this(config: XMLCalabashRuntime, output: XOutput, outputs: List[String]) = { this(config, output, Some(outputs)) } override def consume(port: String, message: Message): Unit = { - logger.debug(s"Received message on ${port}") + logger.debug(s"PrintingConsumer received message on ${port}") message match { case msg: XProcItemMessage => // Check that the message content type is allowed on the output port @@ -60,7 +60,7 @@ class PrintingConsumer private(config: XMLCalabashRuntime, output: DeclareOutput outstream.write(buf, 0, len) len = instream.read(buf, 0, buf.length) } - logger.debug(s"${outstream.toByteArray.length} byte message") + //logger.debug(s"${outstream.toByteArray.length} byte message") pos.write(outstream.toByteArray) case msg: XdmValueItemMessage => val stream = new ByteArrayOutputStream() @@ -75,7 +75,7 @@ class PrintingConsumer private(config: XMLCalabashRuntime, output: DeclareOutput S9Api.serialize(config.config, msg.item, serializer) val content = stream.toString("UTF-8") - logger.debug(s"Message: ${content}") + //logger.debug(s"Message: ${content}") pos.print(content) if (!content.endsWith("\n")) { pos.println() diff --git a/src/main/scala/com/xmlcalabash/runtime/ProcessMatch.scala b/src/main/scala/com/xmlcalabash/runtime/ProcessMatch.scala index 2b2873c..a32f80e 100644 --- a/src/main/scala/com/xmlcalabash/runtime/ProcessMatch.scala +++ b/src/main/scala/com/xmlcalabash/runtime/ProcessMatch.scala @@ -5,6 +5,7 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.XdmValueItemMessage import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.om.{AttributeInfo, AttributeMap, NameOfNode, NamespaceResolver} import net.sf.saxon.s9api._ import net.sf.saxon.serialize.SerializationProperties @@ -18,15 +19,15 @@ import scala.jdk.CollectionConverters.{IteratorHasAsJava, SeqHasAsJava} class ProcessMatch(config: XMLCalabash, processor: ProcessMatchingNodes, - context: StaticContext, + context: MinimalStaticContext, bindings: Option[Map[String,Message]]) extends SaxonTreeBuilder(config) { - def this(runtime: XMLCalabashRuntime, processor: ProcessMatchingNodes, context: StaticContext) = { + def this(runtime: XMLCalabashRuntime, processor: ProcessMatchingNodes, context: MinimalStaticContext) = { this(runtime.config, processor, context, None) } - def this(runtime: XMLCalabash, processor: ProcessMatchingNodes, context: StaticContext) = { + def this(runtime: XMLCalabash, processor: ProcessMatchingNodes, context: MinimalStaticContext) = { this(runtime, processor, context, None) } - def this(config: XMLCalabash, processor: ProcessMatchingNodes, context: StaticContext, bindings: Map[String,Message]) = { + def this(config: XMLCalabash, processor: ProcessMatchingNodes, context: MinimalStaticContext, bindings: Map[String,Message]) = { this(config, processor, context, Some(bindings)) } @@ -267,7 +268,7 @@ class ProcessMatch(config: XMLCalabash, } } - for ((prefix, uri) <- context.nsBindings) { + for ((prefix, uri) <- context.inscopeNamespaces) { xcomp.declareNamespace(prefix, uri) } diff --git a/src/main/scala/com/xmlcalabash/runtime/SaxonExpressionEvaluator.scala b/src/main/scala/com/xmlcalabash/runtime/SaxonExpressionEvaluator.scala index e27af00..994eb82 100644 --- a/src/main/scala/com/xmlcalabash/runtime/SaxonExpressionEvaluator.scala +++ b/src/main/scala/com/xmlcalabash/runtime/SaxonExpressionEvaluator.scala @@ -7,8 +7,9 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{AnyItemMessage, XdmNodeItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} +import com.xmlcalabash.model.xxml.XArtifactContext import com.xmlcalabash.runtime.params.XPathBindingParams -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.expr.XPathContext import net.sf.saxon.lib.{CollectionFinder, Resource, ResourceCollection} import net.sf.saxon.ma.arrays.ArrayItem @@ -31,18 +32,18 @@ object SaxonExpressionEvaluator { protected val _dynContext = new DynamicVariable[DynamicContext](null) } -class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvaluator { +class SaxonExpressionEvaluator(xproc: XMLCalabashProcessor) extends ExpressionEvaluator { protected val logger: Logger = LoggerFactory.getLogger(this.getClass) def withContext[T](context: DynamicContext)(thunk: => T): T = SaxonExpressionEvaluator._dynContext.withValue(context)(thunk) def dynContext: Option[DynamicContext] = Option(SaxonExpressionEvaluator._dynContext.value) override def newInstance(): SaxonExpressionEvaluator = { - new SaxonExpressionEvaluator(xmlCalabash) + new SaxonExpressionEvaluator(xproc) } override def singletonValue(xpath: Any, context: List[Message], bindings: Map[String, Message], params: Option[BindingParams]): XdmValueItemMessage = { - val xdmval = value(xpath, context, bindings, params).item.asInstanceOf[XdmValue] + val xdmval = value(xpath, context, bindings, params).item if (xdmval.size() == 1) { new XdmValueItemMessage(xdmval, XProcMetadata.XML, xpath.asInstanceOf[XProcExpression].context) @@ -69,7 +70,6 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu override def value(xpath: Any, context: List[Message], bindings: Map[String, Message], params: Option[BindingParams]): XdmValueItemMessage = { val proxies = mutable.HashMap.empty[Any, XdmItem] - // FIXME: this is ugly val exprContext = xpath match { case expr: XProcXPathExpression => Some(expr.context) case expr: XProcVtExpression => Some(expr.context) @@ -77,7 +77,17 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu } val newContext = if (exprContext.isDefined) { - new DynamicContext(exprContext.get.artifact) + exprContext.get match { + case xa: XArtifactContext => + xproc match { + case runtime: XMLCalabashRuntime => + new DynamicContext(runtime, xa.artifact) + case _ => + new DynamicContext(xa.artifact) + } + case _ => + new DynamicContext() + } } else { new DynamicContext() } @@ -226,13 +236,8 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu proxies: Map[Any,XdmItem], options: XPathBindingParams): XdmValue = { val patchBindings = mutable.HashMap.empty[QName, XdmValue] - for ((str, value) <- xpath.context.statics) { - value match { - case msg: XdmValueItemMessage => - patchBindings.put(ValueParser.parseClarkName(str), msg.item) - case _ => - throw XProcException.xiInvalidMessage(None, value) - } + for ((str, constant) <- xpath.context.inscopeConstants) { + patchBindings.put(str, constant.constantValue.get.item) } for ((str, value) <- bindings) { @@ -282,7 +287,7 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu } } - val typeFactory = new ItemTypeFactory(xmlCalabash.processor) + val typeFactory = new ItemTypeFactory(xproc.processor) val untypedAtomicType = typeFactory.getAtomicType(XProcConstants.xs_untypedAtomic) xdmval = new XdmAtomicValue(sbuf.toString, untypedAtomicType) } else { @@ -336,15 +341,15 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu private def computeValue(xpath: String, as: Option[SequenceType], contextItem: List[Message], - exprContext: StaticContext, + exprContext: MinimalStaticContext, bindings: Map[QName,XdmValue], proxies: Map[Any, XdmItem], extensionsOk: Boolean, params: XPathBindingParams, useCollection: Boolean): XdmValue = { - val config = xmlCalabash.processor.getUnderlyingConfiguration + val config = xproc.processor.getUnderlyingConfiguration - val sconfig = xmlCalabash.processor.getUnderlyingConfiguration + val sconfig = xproc.processor.getUnderlyingConfiguration val curfinder = sconfig.getCollectionFinder if (useCollection) { @@ -353,7 +358,7 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu } try { - val xcomp = xmlCalabash.processor.newXPathCompiler() + val xcomp = xproc.processor.newXPathCompiler() val baseURI = if (exprContext.baseURI.isDefined) { exprContext.baseURI.get } else { @@ -366,13 +371,13 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu for (varname <- bindings.keySet) { xcomp.declareVariable(varname) } - for (varname <- params.statics.keySet) { + for (varname <- params.constants.keySet) { if (!bindings.contains(varname)) { xcomp.declareVariable(varname) } } - for ((prefix, uri) <- exprContext.nsBindings) { + for ((prefix, uri) <- exprContext.inscopeNamespaces) { xcomp.declareNamespace(prefix, uri) } @@ -395,7 +400,7 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu for ((varname, varvalue) <- bindings) { selector.setVariable(varname, varvalue) } - for ((varname, varvalue) <- params.statics) { + for ((varname, varvalue) <- params.constants) { if (!bindings.contains(varname)) { selector.setVariable(varname, varvalue) } @@ -521,9 +526,9 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu } msg.metadata match { - case xproc: XProcMetadata => - val props = xproc.properties - val builder = new SaxonTreeBuilder(xmlCalabash) + case meta: XProcMetadata => + val props = meta.properties + val builder = new SaxonTreeBuilder(xproc) builder.startDocument(None) builder.addStartElement(XProcConstants.c_document_properties) for ((key,value) <- props) { @@ -545,7 +550,7 @@ class SaxonExpressionEvaluator(xmlCalabash: XMLCalabash) extends ExpressionEvalu } private def emptyProxy(): XdmNode = { - val builder = new SaxonTreeBuilder(xmlCalabash) + val builder = new SaxonTreeBuilder(xproc) builder.startDocument(None) builder.addStartElement(XProcConstants.c_document_properties) builder.addEndElement() diff --git a/src/main/scala/com/xmlcalabash/runtime/SaxonExpressionOptions.scala b/src/main/scala/com/xmlcalabash/runtime/SaxonExpressionOptions.scala index 5790ef2..c5b49ee 100644 --- a/src/main/scala/com/xmlcalabash/runtime/SaxonExpressionOptions.scala +++ b/src/main/scala/com/xmlcalabash/runtime/SaxonExpressionOptions.scala @@ -81,7 +81,7 @@ class SaxonExpressionOptions private(map: Option[Map[String,Any]]) { private def checkBoolean(key: String, value: Any): Boolean = { value match { case b: Boolean => b - case _ => throw XProcException.xdBadValue(value.toString, "boolean", None) + case _ => throw XProcException.xdBadType(value.toString, "boolean", None) } } @@ -96,7 +96,7 @@ class SaxonExpressionOptions private(map: Option[Map[String,Any]]) { private def checkDouble(key: String, value: Any): Double = { value match { case d: Double => d - case _ => throw XProcException.xdBadValue(value.toString, "double", None) + case _ => throw XProcException.xdBadType(value.toString, "double", None) } } @@ -111,7 +111,7 @@ class SaxonExpressionOptions private(map: Option[Map[String,Any]]) { private def checkQName(key: String, value: Any): QName = { value match { case q: QName => q - case _ => throw XProcException.xdBadValue(value.toString, "QName", None) + case _ => throw XProcException.xdBadType(value.toString, "QName", None) } } } diff --git a/src/main/scala/com/xmlcalabash/runtime/StaticContext.scala b/src/main/scala/com/xmlcalabash/runtime/StaticContext.scala index 2348346..a6cc8b6 100644 --- a/src/main/scala/com/xmlcalabash/runtime/StaticContext.scala +++ b/src/main/scala/com/xmlcalabash/runtime/StaticContext.scala @@ -4,52 +4,32 @@ import java.net.URI import com.jafpl.graph.Location import com.jafpl.messages.Message import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.model.xml.{Artifact, NameBinding} -import com.xmlcalabash.util.S9Api -import net.sf.saxon.s9api.XdmNode +import com.xmlcalabash.model.xxml.{XArtifact, XNameBinding, XOption} +import com.xmlcalabash.util.{MinimalStaticContext, S9Api, URIUtils} +import net.sf.saxon.s9api.{QName, XdmNode} import scala.collection.mutable -class StaticContext(val config: XMLCalabash, val artifact: Option[Artifact]) { +class StaticContext(val config: XMLCalabash) extends MinimalStaticContext() { protected var _baseURI: Option[URI] = None protected var _inScopeNS = Map.empty[String,String] protected var _location: Option[Location] = None - protected var _statics = Map.empty[String,Message] + protected var _constants = Map.empty[String,Message] - def this(config: XMLCalabash) = { - this(config, None) + def this(runtime: XMLCalabashRuntime) = { + this(runtime.config) } - def this(config: XMLCalabashRuntime) = { - this(config.config, None) - } - - def this(config: XMLCalabash, artifact: Artifact) = { - this(config, Some(artifact)) - } - - def this(runtime: XMLCalabashRuntime, artifact: Option[Artifact]) = { - this(runtime.config, artifact) - } - - def this(runtime: XMLCalabashRuntime, artifact: Artifact) = { - this(runtime.config, Some(artifact)) - } - - def this(context: StaticContext, artifact: Option[Artifact]) = { - this(context.config, artifact) + def this(context: StaticContext) = { + this(context.config) _baseURI = context._baseURI _inScopeNS = context._inScopeNS _location = context._location - _statics = context._statics - } - - def this(context: StaticContext, artifact: Artifact) = { - this(context, Some(artifact)) + _constants = context._constants } - def this(config: XMLCalabash, artifact: Option[Artifact], node: XdmNode) = { - this(config, artifact) + def this(config: XMLCalabash, node: XdmNode) = { + this(config) _baseURI = Option(node.getBaseURI) _inScopeNS = S9Api.inScopeNamespaces(node) _location = Some(new XProcLocation(node)) @@ -70,19 +50,23 @@ class StaticContext(val config: XMLCalabash, val artifact: Option[Artifact]) { _location = Some(loc) } - def statics: Map[String,Message] = _statics + override def inscopeNamespaces: Map[String, String] = _inScopeNS + + override def inscopeConstants: Map[QName, XOption] = Map() + + def constants: Map[String,Message] = _constants - def withStatics(statics: Map[String,Message]): StaticContext = { - val context = new StaticContext(this, artifact) - context._statics = statics + def withConstants(constants: Map[String,Message]): StaticContext = { + val context = new StaticContext(this) + context._constants = constants context } - def withStatics(bindings: List[NameBinding]): StaticContext = { - val statics = mutable.HashMap.empty[String, Message] + def withConstants(bindings: List[XNameBinding]): StaticContext = { + val constants = mutable.HashMap.empty[String, Message] for (bind <- bindings) { - statics.put(bind.name.getClarkName, bind.staticValue.get) + constants.put(bind.name.getClarkName, bind.constantValue.get) } - withStatics(statics.toMap) + withConstants(constants.toMap) } } diff --git a/src/main/scala/com/xmlcalabash/runtime/StepExecutable.scala b/src/main/scala/com/xmlcalabash/runtime/StepExecutable.scala index 214f106..8be9554 100644 --- a/src/main/scala/com/xmlcalabash/runtime/StepExecutable.scala +++ b/src/main/scala/com/xmlcalabash/runtime/StepExecutable.scala @@ -1,7 +1,7 @@ package com.xmlcalabash.runtime -import com.xmlcalabash.config.StepSignature +import com.xmlcalabash.model.xxml.XDeclareStep trait StepExecutable extends XmlStep { - def signature: StepSignature + def declaration: XDeclareStep } diff --git a/src/main/scala/com/xmlcalabash/runtime/StepProxy.scala b/src/main/scala/com/xmlcalabash/runtime/StepProxy.scala index 0acbef4..48cb876 100644 --- a/src/main/scala/com/xmlcalabash/runtime/StepProxy.scala +++ b/src/main/scala/com/xmlcalabash/runtime/StepProxy.scala @@ -8,7 +8,7 @@ import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{AnyItemMessage, XdmNodeItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} -import com.xmlcalabash.runtime.params.StepParams +import com.xmlcalabash.model.xxml.XStaticContext import com.xmlcalabash.util.{MediaType, S9Api, TypeUtils} import net.sf.saxon.ma.map.MapItem import net.sf.saxon.s9api.{Axis, QName, SequenceType, XdmAtomicValue, XdmItem, XdmMap, XdmNode, XdmNodeKind, XdmValue} @@ -20,7 +20,7 @@ import java.net.URI import scala.collection.mutable import scala.collection.mutable.ListBuffer -class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutable, params: Option[ImplParams], staticContext: StaticContext) extends Step with XProcDataConsumer { +class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutable, staticContext: XStaticContext) extends Step with XProcDataConsumer { private var _id: String = _ private val openStreams = ListBuffer.empty[InputStream] protected val logger: Logger = LoggerFactory.getLogger(this.getClass) @@ -111,15 +111,15 @@ class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutabl new QName("", bindmsg.name) } - if (step.signature.stepType.isDefined) { - val ns = step.signature.stepType.get.getNamespaceURI + if (step.declaration.stepType.isDefined) { + val ns = step.declaration.stepType.get.getNamespaceURI if ((ns == XProcConstants.ns_p && qname == XProcConstants._message) || (ns != XProcConstants.ns_p && qname == XProcConstants.p_message)) { System.err.println(bindmsg.message.toString) return } } else { - if (qname == XProcConstants.p_message) { + if (qname == XProcConstants._message) { System.err.println(bindmsg.message.toString) return } @@ -128,9 +128,9 @@ class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutabl bindings += qname bindingsMap.put(qname.getClarkName, bindmsg.message) - val stepsig = step.signature - if (stepsig.optionNames.contains(qname)) { - val optsig = stepsig.option(qname, staticContext.location) + val decl = step.declaration + if (decl.optionNames.contains(qname)) { + val optsig = decl.option(qname).get val opttype: Option[SequenceType] = optsig.declaredType val optlist: Option[List[XdmAtomicValue]] = optsig.tokenList @@ -173,7 +173,7 @@ class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutabl val xvalue = valuemsg.item.getUnderlyingValue xvalue match { case map: MapItem => - if (optsig.forceQNameKeys) { + if (optsig.qnameKeys) { val qmap = S9Api.forceQNameKeys(map, staticContext) step.receiveBinding(new NameValueBinding(qname, qmap, valuemsg)) } else { @@ -209,6 +209,15 @@ class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutabl override def run(): Unit = { running = true + for (name <- step.declaration.optionNames) { + if (!bindings.contains(name)) { + if (staticContext.inscopeConstants.contains(name)) { + val value = staticContext.inscopeConstants(name) + step.receiveBinding(new NameValueBinding(name, value.constantValue.get)) + } + } + } + for (port <- inputBuffer.keySet) { for (message <- inputBuffer(port)) { processInput(port, message) @@ -223,26 +232,6 @@ class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutabl } outputBuffer.clear() - // If there are statically computed options for this step, pass them along - if (params.isDefined && params.get.isInstanceOf[StepParams]) { - val atomic = params.get.asInstanceOf[StepParams] - for ((name,value) <- atomic.staticallyComputedOptions) { - val bindmsg = new BindingMessage(name, value) - receiveBinding(bindmsg) - } - } - - for (qname <- step.signature.optionNames) { - if (!bindings.contains(qname)) { - val optsig = step.signature.option(qname, staticContext.location) - val opttype: Option[SequenceType] = optsig.declaredType - if (optsig.defaultSelect.isDefined) { - val value = TypeUtils.castAtomicAs(new XdmAtomicValue(optsig.defaultSelect.get), opttype, staticContext) - step.receiveBinding(new NameValueBinding(qname, value, XProcMetadata.ANY, staticContext)) - } - } - } - try { DynamicContext.withContext(dynamicContext) { step.run(staticContext) @@ -265,7 +254,7 @@ class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutabl try { stream.close() } catch { - case ex: IOException => () + case _: IOException => () case ex: Exception => thrown = Some(ex) } @@ -311,7 +300,7 @@ class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutabl received.put(port, received.getOrElse(port, 1)) inputSpec.checkInputCardinality(port, received(port)) - val mtypes = step.signature.input(port, staticContext.location).contentTypes + val mtypes = step.declaration.input(port).contentTypes if (mtypes.nonEmpty) { val ctype = message match { case msg: XdmNodeItemMessage => Some(msg.metadata.contentType) @@ -399,7 +388,7 @@ class StepProxy(config: XMLCalabashRuntime, stepType: QName, step: StepExecutabl } // Is the content type ok? - val mtypes = step.signature.output(port, staticContext.location).contentTypes + val mtypes = step.declaration.output(port).contentTypes if (mtypes.nonEmpty) { // FIXME: rethrow XC0070 with a location if (!metadata.contentType.allowed(mtypes)) { diff --git a/src/main/scala/com/xmlcalabash/runtime/StepRunner.scala b/src/main/scala/com/xmlcalabash/runtime/StepRunner.scala index ae31be0..476b5e7 100644 --- a/src/main/scala/com/xmlcalabash/runtime/StepRunner.scala +++ b/src/main/scala/com/xmlcalabash/runtime/StepRunner.scala @@ -7,15 +7,14 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.config.StepSignature import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{AnyItemMessage, XdmValueItemMessage} -import com.xmlcalabash.model.util.XProcConstants -import com.xmlcalabash.model.xml.DeclareStep -import com.xmlcalabash.util.{S9Api, XProcVarValue} -import net.sf.saxon.s9api.{QName, XdmItem, XdmNode, XdmValue} +import com.xmlcalabash.model.xxml.{XDeclareStep, XInput, XOption, XOutput, XStaticContext} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, XProcVarValue} +import net.sf.saxon.s9api.QName import scala.collection.mutable import scala.collection.mutable.ListBuffer -class StepRunner(private val pruntime: XMLCalabash, val decl: DeclareStep, val signature: StepSignature) extends StepExecutable { +class StepRunner(val decl: XDeclareStep) extends StepExecutable { private var runtime: XMLCalabashRuntime = _ private var _location = Option.empty[Location] private val consumers = mutable.HashMap.empty[String, ConsumerMap] @@ -25,32 +24,40 @@ class StepRunner(private val pruntime: XMLCalabash, val decl: DeclareStep, val s private val cardMap = mutable.HashMap.empty[String,PortCardinality] private val typeMap = mutable.HashMap.empty[String,List[String]] - for (port <- signature.inputPorts) { - val portSig = signature.input(port, decl.location) - portSig.cardinality match { - case "1" => cardMap.put(portSig.port, new PortCardinality(1,1)) - case "*" => cardMap.put(portSig.port, new PortCardinality(0)) - case "+" => cardMap.put(portSig.port, new PortCardinality(1)) - case _ => throw new RuntimeException("WTF? Cardinality=" + portSig.cardinality) + for (input <- decl.children[XInput]) { + if (input.sequence) { + cardMap.put(input.port, new PortCardinality(0)) + } else { + cardMap.put(input.port, new PortCardinality(1, 1)) + } + + val ctypes = ListBuffer.empty[String] + for (ctype <- input.contentTypes) { + ctypes += ctype.toString } - typeMap.put(portSig.port, List("application/octet-stream")) // FIXME: THIS IS A LIE + typeMap.put(input.port, ctypes.toList) } private val iSpec = new XmlPortSpecification(cardMap.toMap, typeMap.toMap) cardMap.clear() typeMap.clear() - for (port <- signature.outputPorts) { - val portSig = signature.output(port, decl.location) - portSig.cardinality match { - case "1" => cardMap.put(portSig.port, new PortCardinality(1,1)) - case "*" => cardMap.put(portSig.port, new PortCardinality(0)) - case "+" => cardMap.put(portSig.port, new PortCardinality(1)) - case _ => throw new RuntimeException("WTF? Cardinality=" + portSig.cardinality) + for (output <- decl.children[XOutput]) { + if (output.sequence) { + cardMap.put(output.port, new PortCardinality(0)) + } else { + cardMap.put(output.port, new PortCardinality(1, 1)) } - typeMap.put(portSig.port, List("application/octet-stream")) // FIXME: THIS IS A LIE + + val ctypes = ListBuffer.empty[String] + for (ctype <- output.contentTypes) { + ctypes += ctype.toString + } + typeMap.put(output.port, ctypes.toList) } private val oSpec = new XmlPortSpecification(cardMap.toMap, typeMap.toMap) + override def declaration: XDeclareStep = decl + override def inputSpec: XmlPortSpecification = iSpec override def outputSpec: XmlPortSpecification = oSpec @@ -59,8 +66,8 @@ class StepRunner(private val pruntime: XMLCalabash, val decl: DeclareStep, val s override def setConsumer(consumer: XProcDataConsumer): Unit = { // It's too early to set in the runtime, save for later - for (port <- signature.outputPorts) { - consumers.put(port, new ConsumerMap(port, consumer)) + for (output <- decl.children[XOutput]) { + consumers.put(output.port, new ConsumerMap(output.port, consumer)) } } @@ -70,7 +77,7 @@ class StepRunner(private val pruntime: XMLCalabash, val decl: DeclareStep, val s override def receiveBinding(variable: NameValueBinding): Unit = { // It's too early to set in the runtime, save for later - bindings.put(variable.name, new XProcVarValue(variable.value, new StaticContext(variable.context, decl))) + bindings.put(variable.name, new XProcVarValue(variable.value, new XStaticContext())) // FIXME: context? } // Input to the pipeline @@ -98,7 +105,7 @@ class StepRunner(private val pruntime: XMLCalabash, val decl: DeclareStep, val s _usedPorts = ports } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { //println("=======================================") //decl.dump() diff --git a/src/main/scala/com/xmlcalabash/runtime/StepWrapper.scala b/src/main/scala/com/xmlcalabash/runtime/StepWrapper.scala index 617b61c..2e6628b 100644 --- a/src/main/scala/com/xmlcalabash/runtime/StepWrapper.scala +++ b/src/main/scala/com/xmlcalabash/runtime/StepWrapper.scala @@ -5,9 +5,16 @@ import com.jafpl.runtime.RuntimeConfiguration import com.jafpl.steps.BindingSpecification import com.xmlcalabash.XMLCalabash import com.xmlcalabash.config.StepSignature +import com.xmlcalabash.model.xxml.{XDeclareStep, XOption} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{QName, XdmValue} -class StepWrapper(protected[xmlcalabash] val step: XmlStep, val signature: StepSignature) extends StepExecutable { +import scala.collection.mutable + +class StepWrapper(protected[xmlcalabash] val step: XmlStep, decl: XDeclareStep) extends StepExecutable { + private val seenOptions = mutable.HashSet.empty[QName] + + override def declaration: XDeclareStep = decl override def inputSpec: XmlPortSpecification = step.inputSpec override def outputSpec: XmlPortSpecification = step.outputSpec override def bindingSpec: BindingSpecification = step.bindingSpec @@ -15,6 +22,7 @@ class StepWrapper(protected[xmlcalabash] val step: XmlStep, val signature: StepS override def setLocation(location: Location): Unit = step.setLocation(location) override def receiveBinding(variable: NameValueBinding): Unit = { + seenOptions += variable.name step.receiveBinding(variable) } override def receive(port: String, item: Any, metadata: XProcMetadata): Unit = { @@ -26,7 +34,14 @@ class StepWrapper(protected[xmlcalabash] val step: XmlStep, val signature: StepS override def initialize(config: RuntimeConfiguration): Unit = { step.initialize(config) } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { + for (option <- decl.children[XOption]) { + if (!seenOptions.contains(option.name) && context.inscopeConstants.contains(option.name)) { + val value = new NameValueBinding(option.name, context.inscopeConstants(option.name).constantValue.get) + step.receiveBinding(value) + } + } + step.run(context) } override def reset(): Unit = step.reset() diff --git a/src/main/scala/com/xmlcalabash/runtime/XMLCalabashProcessor.scala b/src/main/scala/com/xmlcalabash/runtime/XMLCalabashProcessor.scala new file mode 100644 index 0000000..cc056fa --- /dev/null +++ b/src/main/scala/com/xmlcalabash/runtime/XMLCalabashProcessor.scala @@ -0,0 +1,7 @@ +package com.xmlcalabash.runtime + +import net.sf.saxon.s9api.Processor + +trait XMLCalabashProcessor { + def processor: Processor +} diff --git a/src/main/scala/com/xmlcalabash/runtime/XMLCalabashRuntime.scala b/src/main/scala/com/xmlcalabash/runtime/XMLCalabashRuntime.scala index 0ab4670..ef4d4a1 100644 --- a/src/main/scala/com/xmlcalabash/runtime/XMLCalabashRuntime.scala +++ b/src/main/scala/com/xmlcalabash/runtime/XMLCalabashRuntime.scala @@ -2,33 +2,31 @@ package com.xmlcalabash.runtime import com.jafpl.config.Jafpl import com.jafpl.exceptions.JafplException -import com.jafpl.graph.Graph +import com.jafpl.graph.{Graph, Node} import com.jafpl.messages.Message import com.jafpl.runtime.{GraphRuntime, RuntimeConfiguration} import com.jafpl.steps.DataConsumer import com.jafpl.util.{ErrorListener, TraceEventManager} import com.xmlcalabash.XMLCalabash -import com.xmlcalabash.config.{DocumentManager, DocumentRequest, DocumentResponse, Signatures, XProcConfigurer} +import com.xmlcalabash.config.{DocumentManager, DocumentRequest, Signatures, XProcConfigurer} import com.xmlcalabash.exceptions.{ConfigurationException, ExceptionCode, ModelException, XProcException} import com.xmlcalabash.messages.{AnyItemMessage, XdmNodeItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.{ExpressionParser, XProcConstants} -import com.xmlcalabash.model.xml.{Artifact, DataSource, DeclareStep, Document, Empty, Inline} -import com.xmlcalabash.steps.internal.InlineExpander -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.model.xxml.{XArtifact, XDeclareStep, XDocument, XEmpty, XInline, XInput, XStaticContext} +import com.xmlcalabash.steps.internal.{InlineExpander, XPathSelector} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, TypeUtils} import com.xmlcalabash.util.stores.{DataStore, FallbackDataStore, FileDataStore, HttpDataStore} import net.sf.saxon.lib.{ModuleURIResolver, UnparsedTextURIResolver} import net.sf.saxon.s9api.{Processor, QName, XdmAtomicValue, XdmNode, XdmValue} import org.slf4j.{Logger, LoggerFactory} import org.xml.sax.EntityResolver -import java.io.{File, FileOutputStream} import java.net.URI -import java.nio.charset.StandardCharsets import javax.xml.transform.URIResolver import scala.collection.mutable import scala.collection.mutable.ListBuffer -class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends RuntimeConfiguration { +class XMLCalabashRuntime protected[xmlcalabash] (val decl: XDeclareStep) extends XMLCalabashProcessor with RuntimeConfiguration { val config: XMLCalabash = decl.config //FIXME: why? @@ -47,54 +45,45 @@ class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends private var _episode = config.computeEpisode private var _defaultSerializationOptions: Map[String,Map[QName,String]] = Map.empty[String,Map[QName,String]] private var _trim_inline_whitespace = config.trimInlineWhitespace - private val idMap = mutable.HashMap.empty[String,Artifact] + private val idMap = mutable.HashMap.empty[String,XArtifact] private var ran = false private var _signatures: Signatures = _ private var runtime: GraphRuntime = _ + private var _staticOptions: Map[QName,XdmValueItemMessage] = _ private var _datastore = Option.empty[DataStore] - private val defaultInputs = mutable.HashMap.empty[String, List[DocumentRequest]] private val _usedPorts = mutable.HashSet.empty[String] + private val _graphNodes = mutable.HashMap.empty[XArtifact, Node] val jafpl: Jafpl = Jafpl.newInstance() val graph: Graph = jafpl.newGraph() - if (decl._name.isDefined) { - graph.label = decl._name.get + if (decl.name.isDefined) { + graph.label = decl.name.get } - protected[xmlcalabash] def init(decl: DeclareStep): Unit = { - try { - for (input <- decl.inputs) { - if (input.defaultInputs.nonEmpty) { - val defaults = ListBuffer.empty[DocumentRequest] - for (default <- input.defaultInputs) { - default match { - case _: Empty => - () - case inline: Inline => - val expander = new InlineExpander(inline) - if (inline.documentProperties.isDefined) { - expander.documentProperties = inline.documentProperties.get - } - defaults += expander.loadDocument(inline.expandText) - case doc: Document => - defaults += doc.loadDocument() - case _ => - throw XProcException.xiThisCantHappen(s"Unexpected default input type: ${default}", None) - } - } - if (defaults.nonEmpty) { - defaultInputs.put(input.port, defaults.toList) - } - } - } + def hasNode(art: XArtifact): Boolean = { + _graphNodes.contains(art) + } + + def node(art: XArtifact): Node = { + _graphNodes(art) + } + def addNode(art: XArtifact, node: Node): Unit = { + _graphNodes.put(art, node) + } + def staticOptions: Map[QName, XdmValueItemMessage] = _staticOptions + + protected[xmlcalabash] def init(decl: XDeclareStep): Unit = { + try { config.debugOptions.dumpPipeline(decl) config.debugOptions.dumpOpenGraph(decl, graph) runtime = new GraphRuntime(graph, this) config.debugOptions.dumpGraph(decl, graph) + _staticOptions = config.staticOptions + runtime.traceEventManager = _traceEventManager } catch { case ex: JafplException => @@ -111,15 +100,15 @@ class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends // =================================================================================== - def inputs: List[String] = decl.inputPorts - def outputs: List[String] = decl.outputPorts + def inputs: Set[String] = decl.inputPorts + def outputs: Set[String] = decl.outputPorts protected[runtime] def inputMessage(port: String, msg: Message): Unit = { runtime.inputs(port).send(msg) } def input(port: String, item: Any, metadata: XProcMetadata): Unit = { - val context = new StaticContext(this) + val context = new XStaticContext() item match { case xnode: XdmNode => input(port, new XdmNodeItemMessage(xnode, metadata, context)) @@ -149,9 +138,13 @@ class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends decl.output(port).serialization } - def option(name: QName, value: XdmValue, context: StaticContext): Unit = { + def option(name: QName, value: XdmValue, context: MinimalStaticContext): Unit = { if (runtime.bindings.contains(name.getClarkName)) { - runtime.bindings(name.getClarkName).setValue(new XdmValueItemMessage(value, XProcMetadata.XML, context)) + val optdecl = decl.option(name).get + val typeUtils = new TypeUtils(processor, context) + val msg = new XdmValueItemMessage(value, XProcMetadata.ANY, context) + val cmsg = typeUtils.convertType(name, msg, optdecl.declaredType, optdecl.tokenList) + runtime.bindings(name.getClarkName).setValue(cmsg) } } @@ -164,11 +157,45 @@ class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends throw new RuntimeException("You must call reset() before running a pipeline a second time.") } - for ((port, defaults) <- defaultInputs) { - if (!_usedPorts.contains(port)) { + for (xinput <- decl.children[XInput]) { + if (!_usedPorts.contains(xinput.port) && xinput.defaultInputs.nonEmpty) { + val defaults = ListBuffer.empty[DocumentRequest] + for (default <- xinput.defaultInputs) { + default match { + case _: XEmpty => + () + case inline: XInline => + val expander = new InlineExpander(inline) + if (inline.documentProperties.isDefined) { + expander.documentProperties = inline.documentProperties.get + } + expander.copyStaticOptionsToBindings(this) + defaults += expander.loadDocument(inline.expandText) + case doc: XDocument => + defaults += doc.loadDocument() + case _ => + throw XProcException.xiThisCantHappen(s"Unexpected default input type: ${default}", None) + } + } for (source <- defaults) { val resp = config.documentManager.parse(source) - input(port, resp.value, new XProcMetadata(resp.contentType, resp.props)) + if (xinput.select.isDefined) { + val items = Tuple2(resp.value, new XProcMetadata(resp.contentType, resp.props)) + val bindings = mutable.HashMap.empty[String, Message] + for ((name,value) <- config.staticOptions) { + bindings.put(name.getClarkName, value) + } + val xpselector = new XPathSelector(config, List(items), xinput.select.get, xinput.staticContext, bindings.toMap) + val results = xpselector.select() + if (results.length != 1 && !xinput.sequence) { + throw XProcException.xdInputSequenceNotAllowed(xinput.port, None) + } + for (result <- results) { + input(xinput.port, result, new XProcMetadata(resp.contentType, resp.props)) + } + } else { + input(xinput.port, resp.value, new XProcMetadata(resp.contentType, resp.props)) + } } } } @@ -230,7 +257,6 @@ class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends def staticBaseURI: URI = config.staticBaseURI def episode: String = _episode - // FIXME: Setters for these def entityResolver: EntityResolver = _entityResolver def uriResolver: URIResolver = _uriResolver def moduleURIResolver: ModuleURIResolver = _moduleURIResolver @@ -252,7 +278,10 @@ class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends } override def traceEnabled(trace: String): Boolean = _traceEventManager.traceEnabled(trace) - override def expressionEvaluator: SaxonExpressionEvaluator = config.expressionEvaluator + // We need expression evaluators with access to the *runtime* + private val _expressionEvaluator = new SaxonExpressionEvaluator(this) + override def expressionEvaluator: SaxonExpressionEvaluator = _expressionEvaluator + def expressionParser: ExpressionParser = config.expressionParser def datastore: DataStore = { @@ -267,11 +296,11 @@ class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends // ==================================================================================== - def addNode(id: String, artifact: Artifact): Unit = { + def addNode(id: String, artifact: XArtifact): Unit = { idMap.put(id, artifact) } - def node(id: String): Option[Artifact] = idMap.get(id) + def node(id: String): Option[XArtifact] = idMap.get(id) def signatures: Signatures = { @@ -301,32 +330,4 @@ class XMLCalabashRuntime protected[xmlcalabash] (val decl: DeclareStep) extends def trimInlineWhitespace_=(trim: Boolean): Unit = { _trim_inline_whitespace = trim } - - // ============================================================================================== - - def stepImplementation(stepType: QName, staticContext: StaticContext): StepWrapper = { - stepImplementation(stepType, staticContext, None) - } - - def stepImplementation(stepType: QName, staticContext: StaticContext, implParams: Option[ImplParams]): StepWrapper = { - val location = staticContext.location - - if (!_signatures.stepTypes.contains(stepType)) { - throw new ModelException(ExceptionCode.NOTYPE, stepType.toString, location) - } - - val sig = _signatures.step(stepType) - val implClass = sig.implementation - if (implClass.isEmpty) { - throw new ModelException(ExceptionCode.NOIMPL, stepType.toString, location) - } - - val klass = Class.forName(implClass.head).getDeclaredConstructor().newInstance() - klass match { - case step: XmlStep => - new StepWrapper(step, sig) - case _ => - throw new ModelException(ExceptionCode.IMPLNOTSTEP, stepType.toString, location) - } - } } diff --git a/src/main/scala/com/xmlcalabash/runtime/XProcExpression.scala b/src/main/scala/com/xmlcalabash/runtime/XProcExpression.scala index 29bfcad..a11e548 100644 --- a/src/main/scala/com/xmlcalabash/runtime/XProcExpression.scala +++ b/src/main/scala/com/xmlcalabash/runtime/XProcExpression.scala @@ -1,7 +1,9 @@ package com.xmlcalabash.runtime -class XProcExpression(val context: StaticContext, val extensionFunctionsAllowed: Boolean) { - def this(context: StaticContext) = { +import com.xmlcalabash.util.MinimalStaticContext + +class XProcExpression(val context: MinimalStaticContext, val extensionFunctionsAllowed: Boolean) { + def this(context: MinimalStaticContext) = { this(context, false) } diff --git a/src/main/scala/com/xmlcalabash/runtime/XProcMetadata.scala b/src/main/scala/com/xmlcalabash/runtime/XProcMetadata.scala index 97981ce..3df5281 100644 --- a/src/main/scala/com/xmlcalabash/runtime/XProcMetadata.scala +++ b/src/main/scala/com/xmlcalabash/runtime/XProcMetadata.scala @@ -117,7 +117,10 @@ class XProcMetadata(private val initialContentType: Option[MediaType], if (value.size == 0) { _contentType = Some(MediaType.OCTET_STREAM) } else { - _contentType = Some(MediaType.parse(value.itemAt(0).getStringValue, charset)) // FIXME: what about a sequence? + if (value.size > 1) { + throw XProcException.xiUserError("Content-type property must be a single value, not a list") + } + _contentType = Some(MediaType.parse(value.itemAt(0).getStringValue, charset)) } } else { _contentType = Some(MediaType.OCTET_STREAM) @@ -134,7 +137,10 @@ class XProcMetadata(private val initialContentType: Option[MediaType], if (value.size() == 0) { _baseURI = None } else { - _baseURI = Some(new URI(value.itemAt(0).getStringValue)) // FIXME: what about a sequence? + if (value.size > 1) { + throw XProcException.xiUserError("Base-uri property must be a single value, not a list") + } + _baseURI = Some(new URI(value.itemAt(0).getStringValue)) } } } diff --git a/src/main/scala/com/xmlcalabash/runtime/XProcVtExpression.scala b/src/main/scala/com/xmlcalabash/runtime/XProcVtExpression.scala index 0df8672..bc4e603 100644 --- a/src/main/scala/com/xmlcalabash/runtime/XProcVtExpression.scala +++ b/src/main/scala/com/xmlcalabash/runtime/XProcVtExpression.scala @@ -2,23 +2,24 @@ package com.xmlcalabash.runtime import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.ValueParser +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.expr.parser.ExpressionTool -class XProcVtExpression private(override val context: StaticContext, val params: Option[ExprParams]) extends XProcExpression(context) { +class XProcVtExpression private(override val context: MinimalStaticContext, val params: Option[ExprParams]) extends XProcExpression(context) { private var _avt: List[String] = _ private var _string = false - def this(context: StaticContext, avt: List[String], stringResult: Boolean) = { + def this(context: MinimalStaticContext, avt: List[String], stringResult: Boolean) = { this(context, None) _avt = avt _string = stringResult } - def this(context: StaticContext, avt: List[String]) = { + def this(context: MinimalStaticContext, avt: List[String]) = { this(context, avt, false) } - def this(context: StaticContext, expr: String, stringResult: Boolean) = { + def this(context: MinimalStaticContext, expr: String, stringResult: Boolean) = { this(context, None) val avt = ValueParser.parseAvt(expr) if (avt.isEmpty) { @@ -28,7 +29,7 @@ class XProcVtExpression private(override val context: StaticContext, val params: _string = stringResult } - def this(context: StaticContext, expr: String) = { + def this(context: MinimalStaticContext, expr: String) = { this(context, expr, false) } diff --git a/src/main/scala/com/xmlcalabash/runtime/XProcXPathExpression.scala b/src/main/scala/com/xmlcalabash/runtime/XProcXPathExpression.scala index e4e5553..bfb8acd 100644 --- a/src/main/scala/com/xmlcalabash/runtime/XProcXPathExpression.scala +++ b/src/main/scala/com/xmlcalabash/runtime/XProcXPathExpression.scala @@ -1,24 +1,25 @@ package com.xmlcalabash.runtime import com.xmlcalabash.runtime.params.XPathBindingParams +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{SequenceType, XdmAtomicValue} -class XProcXPathExpression(override val context: StaticContext, +class XProcXPathExpression(override val context: MinimalStaticContext, val expr: String, val as: Option[SequenceType], val values: Option[List[XdmAtomicValue]], val params: Option[XPathBindingParams]) extends XProcExpression(context) { - def this(context: StaticContext, expr: String) = { + def this(context: MinimalStaticContext, expr: String) = { this(context, expr, None, None, None) } - def this(context: StaticContext, expr: String, as: Option[SequenceType]) = { + def this(context: MinimalStaticContext, expr: String, as: Option[SequenceType]) = { this(context, expr, as, None, None) } - def this(context: StaticContext, expr: String, as: Option[SequenceType], values: Option[List[XdmAtomicValue]], params: XPathBindingParams) = { + def this(context: MinimalStaticContext, expr: String, as: Option[SequenceType], values: Option[List[XdmAtomicValue]], params: XPathBindingParams) = { this(context, expr, as, values, Some(params)) } diff --git a/src/main/scala/com/xmlcalabash/runtime/XmlPortSpecification.scala b/src/main/scala/com/xmlcalabash/runtime/XmlPortSpecification.scala index 244baf1..950220d 100644 --- a/src/main/scala/com/xmlcalabash/runtime/XmlPortSpecification.scala +++ b/src/main/scala/com/xmlcalabash/runtime/XmlPortSpecification.scala @@ -123,8 +123,19 @@ class XmlPortSpecification(spec: immutable.Map[String,PortCardinality], if (list.contains("application/octet-stream")) { true } else { - // FIXME: Handle the subtle cases like application/xml+rdf => application/xml - list.contains(contentType) + if (list.contains(contentType)) { + true + } else { + // Match application/xml+rdf against application/xml + // The logic here is a bit crude. + val pos = contentType.indexOf("+") + if (pos > 0) { + val basetype = contentType.substring(0, pos) + list.contains(basetype) + } else { + false + } + } } } else { true diff --git a/src/main/scala/com/xmlcalabash/runtime/XmlStep.scala b/src/main/scala/com/xmlcalabash/runtime/XmlStep.scala index 2cf5cce..61b3b62 100644 --- a/src/main/scala/com/xmlcalabash/runtime/XmlStep.scala +++ b/src/main/scala/com/xmlcalabash/runtime/XmlStep.scala @@ -4,6 +4,7 @@ import com.jafpl.graph.Location import com.jafpl.runtime.RuntimeConfiguration import com.jafpl.steps.BindingSpecification import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{QName, XdmValue} trait XmlStep { @@ -16,7 +17,7 @@ trait XmlStep { def receive(port: String, item: Any, metadata: XProcMetadata): Unit def configure(config: XMLCalabash, stepType: QName, stepName: Option[String], params: Option[ImplParams]): Unit def initialize(config: RuntimeConfiguration): Unit - def run(context: StaticContext): Unit + def run(context: MinimalStaticContext): Unit def reset(): Unit def abort(): Unit def stop(): Unit diff --git a/src/main/scala/com/xmlcalabash/runtime/params/ContentTypeCheckerParams.scala b/src/main/scala/com/xmlcalabash/runtime/params/ContentTypeCheckerParams.scala index 937a8f8..66062ad 100644 --- a/src/main/scala/com/xmlcalabash/runtime/params/ContentTypeCheckerParams.scala +++ b/src/main/scala/com/xmlcalabash/runtime/params/ContentTypeCheckerParams.scala @@ -1,18 +1,19 @@ package com.xmlcalabash.runtime.params import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.runtime.{ImplParams, StaticContext} +import com.xmlcalabash.model.xxml.XStaticContext +import com.xmlcalabash.runtime.ImplParams import com.xmlcalabash.util.MediaType import net.sf.saxon.s9api.QName class ContentTypeCheckerParams(val port: String, val contentTypes: List[MediaType], - val context: StaticContext, + val context: XStaticContext, val select: Option[String], val errCode: QName, val inputPort: Boolean, val sequence: Boolean) extends ImplParams { - def this(port: String, contentTypes: List[MediaType], context: StaticContext, select: Option[String], inputPort: Boolean, sequence: Boolean) = { + def this(port: String, contentTypes: List[MediaType], context: XStaticContext, select: Option[String], inputPort: Boolean, sequence: Boolean) = { this(port, contentTypes, context, select, XProcException.err_xd0038, inputPort, sequence) } } diff --git a/src/main/scala/com/xmlcalabash/runtime/params/DocumentLoaderParams.scala b/src/main/scala/com/xmlcalabash/runtime/params/DocumentLoaderParams.scala index b2b1057..f6b540d 100644 --- a/src/main/scala/com/xmlcalabash/runtime/params/DocumentLoaderParams.scala +++ b/src/main/scala/com/xmlcalabash/runtime/params/DocumentLoaderParams.scala @@ -1,13 +1,13 @@ package com.xmlcalabash.runtime.params -import com.xmlcalabash.runtime.{ImplParams, StaticContext} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.runtime.ImplParams +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} class DocumentLoaderParams(val hrefAvt: List[String], val content_type: Option[MediaType], val parameters: Option[String], val document_properties: Option[String], val context_provided: Boolean, - val context: StaticContext) extends ImplParams { + val context: MinimalStaticContext) extends ImplParams { } diff --git a/src/main/scala/com/xmlcalabash/runtime/params/EmptyLoaderParams.scala b/src/main/scala/com/xmlcalabash/runtime/params/EmptyLoaderParams.scala index 2526c5f..1d0e5a1 100644 --- a/src/main/scala/com/xmlcalabash/runtime/params/EmptyLoaderParams.scala +++ b/src/main/scala/com/xmlcalabash/runtime/params/EmptyLoaderParams.scala @@ -1,7 +1,8 @@ package com.xmlcalabash.runtime.params -import com.xmlcalabash.runtime.{ImplParams, StaticContext} +import com.xmlcalabash.model.xxml.XStaticContext +import com.xmlcalabash.runtime.ImplParams -class EmptyLoaderParams(val context: StaticContext) extends ImplParams { +class EmptyLoaderParams(val context: XStaticContext) extends ImplParams { } diff --git a/src/main/scala/com/xmlcalabash/runtime/params/InlineLoaderParams.scala b/src/main/scala/com/xmlcalabash/runtime/params/InlineLoaderParams.scala index db0a468..4f28ec1 100644 --- a/src/main/scala/com/xmlcalabash/runtime/params/InlineLoaderParams.scala +++ b/src/main/scala/com/xmlcalabash/runtime/params/InlineLoaderParams.scala @@ -1,15 +1,15 @@ package com.xmlcalabash.runtime.params import com.xmlcalabash.runtime.{ImplParams, StaticContext} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.XdmNode class InlineLoaderParams(val document: XdmNode, val content_type: Option[MediaType], val document_properties: Option[String], val encoding: Option[String], - val exclude_inline_prefixes: Option[String], + val exclude_uris: Set[String], val expand_text: Boolean, val context_provided: Boolean, - val context: StaticContext) extends ImplParams { + val context: MinimalStaticContext) extends ImplParams { } diff --git a/src/main/scala/com/xmlcalabash/runtime/params/SelectFilterParams.scala b/src/main/scala/com/xmlcalabash/runtime/params/SelectFilterParams.scala index 9e9b0cd..9829acb 100644 --- a/src/main/scala/com/xmlcalabash/runtime/params/SelectFilterParams.scala +++ b/src/main/scala/com/xmlcalabash/runtime/params/SelectFilterParams.scala @@ -1,6 +1,7 @@ package com.xmlcalabash.runtime.params -import com.xmlcalabash.runtime.{ImplParams, StaticContext, XmlPortSpecification} +import com.xmlcalabash.model.xxml.XStaticContext +import com.xmlcalabash.runtime.{ImplParams, XmlPortSpecification} -class SelectFilterParams(val context: StaticContext, val select: String, val port: String, val ispec: XmlPortSpecification) extends ImplParams { +class SelectFilterParams(val context: XStaticContext, val select: String, val port: String, val sequence: Boolean) extends ImplParams { } diff --git a/src/main/scala/com/xmlcalabash/runtime/params/XPathBindingParams.scala b/src/main/scala/com/xmlcalabash/runtime/params/XPathBindingParams.scala index fdb7b46..321d971 100644 --- a/src/main/scala/com/xmlcalabash/runtime/params/XPathBindingParams.scala +++ b/src/main/scala/com/xmlcalabash/runtime/params/XPathBindingParams.scala @@ -8,7 +8,7 @@ object XPathBindingParams { def EMPTY: XPathBindingParams = _empty } -class XPathBindingParams(val statics: Map[QName, XdmValue], val collection: Boolean) extends BindingParams { +class XPathBindingParams(val constants: Map[QName, XdmValue], val collection: Boolean) extends BindingParams { def this(collection: Boolean) = { this(Map.empty[QName,XdmValue], collection) } diff --git a/src/main/scala/com/xmlcalabash/steps/AddAttribute.scala b/src/main/scala/com/xmlcalabash/steps/AddAttribute.scala index 06500f4..77423bd 100644 --- a/src/main/scala/com/xmlcalabash/steps/AddAttribute.scala +++ b/src/main/scala/com/xmlcalabash/steps/AddAttribute.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{S9Api, TypeUtils} +import com.xmlcalabash.util.{MinimalStaticContext, S9Api, TypeUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap, NamespaceMap} import net.sf.saxon.s9api.{Axis, QName, XdmNode} import net.sf.saxon.value.QNameValue @@ -32,7 +32,7 @@ class AddAttribute() extends DefaultXmlStep with ProcessMatchingNodes { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { attrName = qnameBinding(_attribute_name).get attrValue = stringBinding(_attribute_value) pattern = stringBinding(XProcConstants._match) diff --git a/src/main/scala/com/xmlcalabash/steps/Archive.scala b/src/main/scala/com/xmlcalabash/steps/Archive.scala index aa81f79..83970d5 100644 --- a/src/main/scala/com/xmlcalabash/steps/Archive.scala +++ b/src/main/scala/com/xmlcalabash/steps/Archive.scala @@ -7,8 +7,9 @@ import com.jafpl.steps.PortCardinality import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} +import com.xmlcalabash.model.xxml.XStaticContext import com.xmlcalabash.runtime.{BinaryNode, NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, S9Api, TypeUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api, TypeUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} import net.sf.saxon.s9api.{Axis, QName, XdmAtomicValue, XdmNode, XdmNodeKind, XdmValue} import org.apache.commons.compress.archivers.ArchiveOutputStream @@ -29,7 +30,7 @@ class Archive extends DefaultXmlStep { private var defaultMethod = "deflated" private var defaultLevel = "default" - private var context: StaticContext = _ + private var context: MinimalStaticContext = _ private var sources = ListBuffer.empty[DocumentWrapper] @@ -88,7 +89,7 @@ class Archive extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { this.context = context format = if (qnameBinding(XProcConstants._format).isDefined) { diff --git a/src/main/scala/com/xmlcalabash/steps/ArchiveManifest.scala b/src/main/scala/com/xmlcalabash/steps/ArchiveManifest.scala index a8de94d..5530207 100644 --- a/src/main/scala/com/xmlcalabash/steps/ArchiveManifest.scala +++ b/src/main/scala/com/xmlcalabash/steps/ArchiveManifest.scala @@ -6,7 +6,7 @@ import java.util.zip.ZipEntry import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} import com.xmlcalabash.runtime.{BinaryNode, NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, TypeUtils, URIUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, TypeUtils, URIUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} import net.sf.saxon.s9api.{QName, XdmArray, XdmValue} import org.apache.commons.compress.archivers.zip.{ZipArchiveEntry, ZipFile} @@ -44,7 +44,7 @@ class ArchiveManifest extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) format = if (qnameBinding(XProcConstants._format).isDefined) { @@ -97,7 +97,7 @@ class ArchiveManifest extends DefaultXmlStep { consumer.receive("result", result, new XProcMetadata(MediaType.XML)) } - private def zipArchive(context: StaticContext, builder: SaxonTreeBuilder): Unit = { + private def zipArchive(context: MinimalStaticContext, builder: SaxonTreeBuilder): Unit = { // ZIP requires random access: https://commons.apache.org/proper/commons-compress/zip.html source match { case bn: BinaryNode => @@ -107,7 +107,7 @@ class ArchiveManifest extends DefaultXmlStep { } } - private def zipArchiveFile(context: StaticContext, builder: SaxonTreeBuilder, zfile: File): Unit = { + private def zipArchiveFile(context: MinimalStaticContext, builder: SaxonTreeBuilder, zfile: File): Unit = { val zipIn = new ZipFile(zfile) val enum = zipIn.getEntries while (enum.hasMoreElements) { diff --git a/src/main/scala/com/xmlcalabash/steps/B64Decode.scala b/src/main/scala/com/xmlcalabash/steps/B64Decode.scala index 41b54ac..49a3d6d 100644 --- a/src/main/scala/com/xmlcalabash/steps/B64Decode.scala +++ b/src/main/scala/com/xmlcalabash/steps/B64Decode.scala @@ -5,7 +5,7 @@ import java.net.URI import java.util.Base64 import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, TypeUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, TypeUtils} import net.sf.saxon.s9api.{QName, XdmNode, XdmValue} class B64Decode extends DefaultXmlStep { @@ -20,7 +20,7 @@ class B64Decode extends DefaultXmlStep { smeta = Some(metadata) } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val baseValue = if (smeta.isDefined) { diff --git a/src/main/scala/com/xmlcalabash/steps/B64Encode.scala b/src/main/scala/com/xmlcalabash/steps/B64Encode.scala index a3f309c..e57f68f 100644 --- a/src/main/scala/com/xmlcalabash/steps/B64Encode.scala +++ b/src/main/scala/com/xmlcalabash/steps/B64Encode.scala @@ -5,7 +5,7 @@ import java.net.URI import java.util.Base64 import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, S9Api, TypeUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api, TypeUtils} import net.sf.saxon.s9api.{QName, Serializer, XdmEmptySequence, XdmNode, XdmValue} import scala.collection.mutable @@ -39,7 +39,7 @@ class B64Encode extends DefaultXmlStep { smeta = Some(metadata) } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val baseValue = if (smeta.isDefined) { diff --git a/src/main/scala/com/xmlcalabash/steps/CastContentType.scala b/src/main/scala/com/xmlcalabash/steps/CastContentType.scala index 836d623..1045fb4 100644 --- a/src/main/scala/com/xmlcalabash/steps/CastContentType.scala +++ b/src/main/scala/com/xmlcalabash/steps/CastContentType.scala @@ -11,7 +11,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.XdmValueItemMessage import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{BinaryNode, NameValueBinding, StaticContext, XProcMetadata, XProcXPathExpression, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, S9Api, TypeUtils, ValueUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api, TypeUtils, ValueUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} import net.sf.saxon.s9api.{Axis, QName, SaxonApiException, XdmAtomicValue, XdmItem, XdmMap, XdmNode, XdmNodeKind, XdmValue} @@ -41,7 +41,7 @@ class CastContentType() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) if (castTo.xmlContentType) { @@ -59,7 +59,7 @@ class CastContentType() extends DefaultXmlStep { } } - def castToXML(context: StaticContext): Unit = { + def castToXML(context: MinimalStaticContext): Unit = { val contentType = metadata.get.contentType contentType.classification match { @@ -142,7 +142,7 @@ class CastContentType() extends DefaultXmlStep { } } - def castToText(context: StaticContext): Unit = { + def castToText(context: MinimalStaticContext): Unit = { val contentType = metadata.get.contentType contentType.classification match { @@ -238,7 +238,7 @@ class CastContentType() extends DefaultXmlStep { consumer.receive("result", builder.result, metadata.get.castTo(castTo)) } - def castToJSON(context: StaticContext): Unit = { + def castToJSON(context: MinimalStaticContext): Unit = { val contentType = metadata.get.contentType contentType.classification match { @@ -341,7 +341,7 @@ class CastContentType() extends DefaultXmlStep { } } - def castToHTML(context: StaticContext): Unit = { + def castToHTML(context: MinimalStaticContext): Unit = { val contentType = metadata.get.contentType contentType.classification match { @@ -373,7 +373,7 @@ class CastContentType() extends DefaultXmlStep { } } - def castToBinary(context: StaticContext): Unit = { + def castToBinary(context: MinimalStaticContext): Unit = { val contentType = metadata.get.contentType contentType.classification match { diff --git a/src/main/scala/com/xmlcalabash/steps/Compress.scala b/src/main/scala/com/xmlcalabash/steps/Compress.scala index 1de6691..e26b8d2 100644 --- a/src/main/scala/com/xmlcalabash/steps/Compress.scala +++ b/src/main/scala/com/xmlcalabash/steps/Compress.scala @@ -5,7 +5,7 @@ import java.util.zip.GZIPOutputStream import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} import com.xmlcalabash.runtime.{BinaryNode, NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmValue} import scala.collection.mutable @@ -34,7 +34,7 @@ class Compress extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) format = qnameBinding(XProcConstants._format) diff --git a/src/main/scala/com/xmlcalabash/steps/Count.scala b/src/main/scala/com/xmlcalabash/steps/Count.scala index d343f9b..1a7f6d7 100644 --- a/src/main/scala/com/xmlcalabash/steps/Count.scala +++ b/src/main/scala/com/xmlcalabash/steps/Count.scala @@ -2,7 +2,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, URIUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, URIUtils} import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmValue} class Count() extends DefaultXmlStep { @@ -36,7 +36,7 @@ class Count() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) sendCount() diff --git a/src/main/scala/com/xmlcalabash/steps/DefaultXmlStep.scala b/src/main/scala/com/xmlcalabash/steps/DefaultXmlStep.scala index f197218..70661ad 100644 --- a/src/main/scala/com/xmlcalabash/steps/DefaultXmlStep.scala +++ b/src/main/scala/com/xmlcalabash/steps/DefaultXmlStep.scala @@ -8,16 +8,17 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.XdmValueItemMessage import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.model.xxml.XStaticContext import com.xmlcalabash.runtime._ import com.xmlcalabash.steps.DefaultXmlStep.showRunningMessage -import com.xmlcalabash.util.{MediaType, S9Api} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api} import net.sf.saxon.`type`.TypeHierarchy import net.sf.saxon.expr.parser.RoleDiagnostic import net.sf.saxon.lib.NamespaceConstant import net.sf.saxon.om.NamespaceMap import net.sf.saxon.s9api._ import net.sf.saxon.trans.XPathException -import net.sf.saxon.value.QNameValue +import net.sf.saxon.value.{QNameValue, UntypedAtomicValue} import org.slf4j.{Logger, LoggerFactory} import java.io.{InputStream, OutputStream} @@ -130,7 +131,7 @@ class DefaultXmlStep extends XmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { runningMessage() if (_location.isEmpty) { _location = context.location @@ -297,6 +298,8 @@ class DefaultXmlStep extends XmlStep { bindings(name).value.getUnderlyingValue match { case qn: QNameValue => Some(new QName(qn.getPrefix, qn.getNamespaceURI, qn.getLocalName)) + case u: UntypedAtomicValue => + Some(bindings(name).context.parseQName(u.getStringValue)) case _ => None } } else { @@ -408,7 +411,7 @@ class DefaultXmlStep extends XmlStep { new XProcMetadata(MediaType.TEXT, props.toMap) } - def serialize(context: StaticContext, source: Any, metadata: XProcMetadata, output: OutputStream): Unit = { + def serialize(context: MinimalStaticContext, source: Any, metadata: XProcMetadata, output: OutputStream): Unit = { source match { case bn: BinaryNode => val bytes = new Array[Byte](8192) @@ -448,7 +451,7 @@ class DefaultXmlStep extends XmlStep { } } - private def serializeJson(context: StaticContext, source: Any, metadata: XProcMetadata, output: OutputStream): Unit = { + private def serializeJson(context: MinimalStaticContext, source: Any, metadata: XProcMetadata, output: OutputStream): Unit = { val expr = new XProcXPathExpression(context, "serialize($map, map {\"method\": \"json\"})") val bindingsMap = mutable.HashMap.empty[String, Message] val vmsg = new XdmValueItemMessage(source.asInstanceOf[XdmValue], XProcMetadata.TEXT, context) diff --git a/src/main/scala/com/xmlcalabash/steps/Delete.scala b/src/main/scala/com/xmlcalabash/steps/Delete.scala index ce047c1..8e28993 100644 --- a/src/main/scala/com/xmlcalabash/steps/Delete.scala +++ b/src/main/scala/com/xmlcalabash/steps/Delete.scala @@ -3,6 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.om.AttributeMap import net.sf.saxon.s9api.XdmNode @@ -20,7 +21,7 @@ class Delete() extends DefaultXmlStep with ProcessMatchingNodes { source_metadata = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { pattern = stringBinding(XProcConstants._match) matcher = new ProcessMatch(config, this, context) diff --git a/src/main/scala/com/xmlcalabash/steps/Error.scala b/src/main/scala/com/xmlcalabash/steps/Error.scala index 7274adb..2c84864 100644 --- a/src/main/scala/com/xmlcalabash/steps/Error.scala +++ b/src/main/scala/com/xmlcalabash/steps/Error.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.TypeUtils +import com.xmlcalabash.util.{MinimalStaticContext, TypeUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap, NamespaceMap} import net.sf.saxon.s9api.{QName, XdmNode} @@ -22,7 +22,7 @@ class Error extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val code = qnameBinding(_code).get diff --git a/src/main/scala/com/xmlcalabash/steps/EscapeMarkup.scala b/src/main/scala/com/xmlcalabash/steps/EscapeMarkup.scala index 4f409e5..d5d8cb8 100644 --- a/src/main/scala/com/xmlcalabash/steps/EscapeMarkup.scala +++ b/src/main/scala/com/xmlcalabash/steps/EscapeMarkup.scala @@ -1,11 +1,10 @@ package com.xmlcalabash.steps import java.io.StringWriter - import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.S9Api +import com.xmlcalabash.util.{MinimalStaticContext, S9Api} import net.sf.saxon.s9api._ class EscapeMarkup() extends DefaultXmlStep { @@ -21,7 +20,7 @@ class EscapeMarkup() extends DefaultXmlStep { metadata = meta } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val options = mapBinding(XProcConstants._serialization) diff --git a/src/main/scala/com/xmlcalabash/steps/Filter.scala b/src/main/scala/com/xmlcalabash/steps/Filter.scala index 0d8e55f..362f715 100644 --- a/src/main/scala/com/xmlcalabash/steps/Filter.scala +++ b/src/main/scala/com/xmlcalabash/steps/Filter.scala @@ -5,6 +5,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{XProcItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XProcXPathExpression, XmlPortSpecification} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.XdmNode import scala.collection.mutable @@ -26,7 +27,7 @@ class Filter extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { val select = stringBinding(XProcConstants._select) val expr = new XProcXPathExpression(context, select) diff --git a/src/main/scala/com/xmlcalabash/steps/Hash.scala b/src/main/scala/com/xmlcalabash/steps/Hash.scala index 80cd099..936e295 100644 --- a/src/main/scala/com/xmlcalabash/steps/Hash.scala +++ b/src/main/scala/com/xmlcalabash/steps/Hash.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{HashUtils, TypeUtils} +import com.xmlcalabash.util.{HashUtils, MinimalStaticContext, TypeUtils} import net.sf.saxon.`type`.BuiltInAtomicType import net.sf.saxon.event.ReceiverOption import net.sf.saxon.om.{AttributeInfo, AttributeMap} @@ -36,7 +36,7 @@ class Hash() extends DefaultXmlStep with ProcessMatchingNodes { this.metadata = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val value = stringBinding(_value).getBytes("UTF-8") diff --git a/src/main/scala/com/xmlcalabash/steps/HttpRequest.scala b/src/main/scala/com/xmlcalabash/steps/HttpRequest.scala index 09b4fa6..482bca6 100644 --- a/src/main/scala/com/xmlcalabash/steps/HttpRequest.scala +++ b/src/main/scala/com/xmlcalabash/steps/HttpRequest.scala @@ -7,7 +7,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.XdmValueItemMessage import com.xmlcalabash.model.util.{ValueParser, XProcConstants} import com.xmlcalabash.runtime.{BinaryNode, NameValueBinding, StaticContext, XProcMetadata, XProcXPathExpression, XmlPortSpecification} -import com.xmlcalabash.util.{InternetProtocolRequest, MediaType} +import com.xmlcalabash.util.{InternetProtocolRequest, MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmMap, XdmValue} import java.io.ByteArrayOutputStream @@ -21,7 +21,7 @@ class HttpRequest() extends DefaultXmlStep { private val sources = ListBuffer.empty[Any] private val sourceMeta = ListBuffer.empty[XProcMetadata] - private var context: StaticContext = _ + private var context: MinimalStaticContext = _ private var href: URI = _ private var method = "" private val headers = mutable.HashMap.empty[String,String] @@ -133,7 +133,7 @@ class HttpRequest() extends DefaultXmlStep { } } - override def run(ctx: StaticContext): Unit = { + override def run(ctx: MinimalStaticContext): Unit = { super.run(ctx) context = ctx diff --git a/src/main/scala/com/xmlcalabash/steps/Insert.scala b/src/main/scala/com/xmlcalabash/steps/Insert.scala index 784050b..c9f1f6b 100644 --- a/src/main/scala/com/xmlcalabash/steps/Insert.scala +++ b/src/main/scala/com/xmlcalabash/steps/Insert.scala @@ -4,6 +4,7 @@ import com.jafpl.steps.PortCardinality import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.om.AttributeMap import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmNode, XdmNodeKind} @@ -43,7 +44,7 @@ class Insert() extends DefaultXmlStep with ProcessMatchingNodes { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) position = stringBinding(_position) diff --git a/src/main/scala/com/xmlcalabash/steps/JavaScript.scala b/src/main/scala/com/xmlcalabash/steps/JavaScript.scala index ea0f007..71742b8 100644 --- a/src/main/scala/com/xmlcalabash/steps/JavaScript.scala +++ b/src/main/scala/com/xmlcalabash/steps/JavaScript.scala @@ -4,7 +4,7 @@ import com.jafpl.runtime.RuntimeConfiguration import com.jafpl.steps.PortCardinality import com.xmlcalabash.model.util.{ValueParser, XProcConstants} import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XMLCalabashRuntime, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.TypeUtils +import com.xmlcalabash.util.{MinimalStaticContext, TypeUtils} import javax.script.ScriptEngineManager import net.sf.saxon.s9api.{QName, XdmNode, XdmValue} @@ -22,11 +22,6 @@ class JavaScript extends DefaultXmlStep { override def outputSpec: XmlPortSpecification = XmlPortSpecification.ANYRESULT - override def initialize(config: RuntimeConfiguration): Unit = { - super.initialize(config) - typeUtils = new TypeUtils(config.asInstanceOf[XMLCalabashRuntime]) - } - override def receive(port: String, item: Any, metadata: XProcMetadata): Unit = { if (port == "script") { item match { @@ -44,8 +39,9 @@ class JavaScript extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) + typeUtils = new TypeUtils(config, context) for (key <- parameters.keySet) { engine.put(key.getLocalName, parameters(key)) diff --git a/src/main/scala/com/xmlcalabash/steps/LabelElements.scala b/src/main/scala/com/xmlcalabash/steps/LabelElements.scala index 4df6187..23bb835 100644 --- a/src/main/scala/com/xmlcalabash/steps/LabelElements.scala +++ b/src/main/scala/com/xmlcalabash/steps/LabelElements.scala @@ -1,13 +1,13 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.messages.{XdmNodeItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime._ +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.`type`.BuiltInAtomicType import net.sf.saxon.event.ReceiverOption import net.sf.saxon.om.{AttributeInfo, AttributeMap, EmptyAttributeMap, FingerprintedQName, NamespaceMap} -import net.sf.saxon.s9api.{Axis, QName, XdmAtomicValue, XdmNode, XdmValue} +import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmNode} import java.net.URI import scala.jdk.CollectionConverters.IterableHasAsScala @@ -18,7 +18,7 @@ class LabelElements() extends DefaultXmlStep with ProcessMatchingNodes { private val _replace = new QName("replace") private val p_index = new QName("p", XProcConstants.ns_p, "index") - private var context: StaticContext = _ + private var context: MinimalStaticContext = _ private var attribute: QName = _ private var label: String = _ private var labelNamespaceBindings = Map.empty[String, String] @@ -40,11 +40,11 @@ class LabelElements() extends DefaultXmlStep with ProcessMatchingNodes { override def receiveBinding(variable: NameValueBinding): Unit = { super.receiveBinding(variable) if (variable.name == _label) { - labelNamespaceBindings = variable.context.nsBindings + labelNamespaceBindings = variable.context.inscopeNamespaces } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) attribute = qnameBinding(_attribute).get diff --git a/src/main/scala/com/xmlcalabash/steps/Load.scala b/src/main/scala/com/xmlcalabash/steps/Load.scala index f906fb1..6a4799e 100644 --- a/src/main/scala/com/xmlcalabash/steps/Load.scala +++ b/src/main/scala/com/xmlcalabash/steps/Load.scala @@ -4,7 +4,7 @@ import java.net.URI import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{BinaryNode, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmMap, XdmValue} import scala.collection.mutable @@ -14,7 +14,7 @@ class Load() extends DefaultXmlStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.ANY - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val href = uriBinding(XProcConstants._href); diff --git a/src/main/scala/com/xmlcalabash/steps/Markdown.scala b/src/main/scala/com/xmlcalabash/steps/Markdown.scala index c3274ea..5ba6c28 100644 --- a/src/main/scala/com/xmlcalabash/steps/Markdown.scala +++ b/src/main/scala/com/xmlcalabash/steps/Markdown.scala @@ -4,7 +4,7 @@ import java.io.ByteArrayInputStream import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.model.util.{ValueParser, XProcConstants} import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmNode, XdmValue} import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer @@ -31,7 +31,7 @@ class Markdown() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val parser = Parser.builder.build diff --git a/src/main/scala/com/xmlcalabash/steps/NamespaceDelete.scala b/src/main/scala/com/xmlcalabash/steps/NamespaceDelete.scala index c3591b6..d4b9a4c 100644 --- a/src/main/scala/com/xmlcalabash/steps/NamespaceDelete.scala +++ b/src/main/scala/com/xmlcalabash/steps/NamespaceDelete.scala @@ -1,11 +1,10 @@ package com.xmlcalabash.steps import java.net.URI - import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime._ -import com.xmlcalabash.util.S9Api +import com.xmlcalabash.util.{MinimalStaticContext, S9Api} import net.sf.saxon.s9api.{Axis, QName, XdmNode, XdmValue} import scala.collection.mutable @@ -23,13 +22,13 @@ class NamespaceDelete() extends DefaultXmlStep { metadata = meta } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) namespaces = mutable.HashSet.empty[String] val prefixes = bindings(XProcConstants._prefixes).value.getUnderlyingValue.getStringValue.split("\\s+") for (prefix <- prefixes) { - val uri = context.nsBindings.get(prefix) + val uri = context.inscopeNamespaces.get(prefix) if (uri.isDefined) { namespaces.add(uri.get) } else { diff --git a/src/main/scala/com/xmlcalabash/steps/NamespaceRename.scala b/src/main/scala/com/xmlcalabash/steps/NamespaceRename.scala index 2bb123c..fafd282 100644 --- a/src/main/scala/com/xmlcalabash/steps/NamespaceRename.scala +++ b/src/main/scala/com/xmlcalabash/steps/NamespaceRename.scala @@ -2,6 +2,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.`type`.{BuiltInAtomicType, Untyped} import net.sf.saxon.event.ReceiverOption import net.sf.saxon.om.{AttributeInfo, AttributeMap, FingerprintedQName, NameOfNode, NamespaceMap, NodeInfo} @@ -29,7 +30,7 @@ class NamespaceRename() extends DefaultXmlStep with ProcessMatchingNodes { metadata = meta } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) from = stringBinding(_from) diff --git a/src/main/scala/com/xmlcalabash/steps/OptionValue.scala b/src/main/scala/com/xmlcalabash/steps/OptionValue.scala index 2bd5eb8..7da0cec 100644 --- a/src/main/scala/com/xmlcalabash/steps/OptionValue.scala +++ b/src/main/scala/com/xmlcalabash/steps/OptionValue.scala @@ -2,7 +2,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmNode, XdmValue} class OptionValue extends DefaultXmlStep { @@ -15,7 +15,7 @@ class OptionValue extends DefaultXmlStep { this.value = Some(variable.value) } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) val builder = new SaxonTreeBuilder(config) diff --git a/src/main/scala/com/xmlcalabash/steps/Pack.scala b/src/main/scala/com/xmlcalabash/steps/Pack.scala index 76f6bb3..f7aace5 100644 --- a/src/main/scala/com/xmlcalabash/steps/Pack.scala +++ b/src/main/scala/com/xmlcalabash/steps/Pack.scala @@ -3,6 +3,7 @@ package com.xmlcalabash.steps import com.jafpl.steps.PortCardinality import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{QName, XdmNode} import scala.collection.mutable.ListBuffer @@ -28,7 +29,7 @@ class Pack() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val wrapName = qnameBinding(_wrapper).get diff --git a/src/main/scala/com/xmlcalabash/steps/Parameters.scala b/src/main/scala/com/xmlcalabash/steps/Parameters.scala index 6557ac5..d927f35 100644 --- a/src/main/scala/com/xmlcalabash/steps/Parameters.scala +++ b/src/main/scala/com/xmlcalabash/steps/Parameters.scala @@ -2,7 +2,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, TypeUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, TypeUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} import net.sf.saxon.s9api.{QName, XdmValue} @@ -19,7 +19,7 @@ class Parameters() extends DefaultXmlStep { } } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) val builder = new SaxonTreeBuilder(config) diff --git a/src/main/scala/com/xmlcalabash/steps/Producer.scala b/src/main/scala/com/xmlcalabash/steps/Producer.scala index aefdb46..b825dba 100644 --- a/src/main/scala/com/xmlcalabash/steps/Producer.scala +++ b/src/main/scala/com/xmlcalabash/steps/Producer.scala @@ -1,7 +1,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import scala.collection.mutable @@ -19,7 +19,7 @@ class Producer() extends DefaultXmlStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.ANYRESULTSEQ - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) for (item <- items) { diff --git a/src/main/scala/com/xmlcalabash/steps/PropertyExtract.scala b/src/main/scala/com/xmlcalabash/steps/PropertyExtract.scala index ae6b65e..1d94a46 100644 --- a/src/main/scala/com/xmlcalabash/steps/PropertyExtract.scala +++ b/src/main/scala/com/xmlcalabash/steps/PropertyExtract.scala @@ -4,7 +4,7 @@ import com.jafpl.steps.PortCardinality import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, TypeUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, TypeUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap, NamespaceMap} import net.sf.saxon.s9api.{XdmAtomicValue, XdmNode} @@ -22,7 +22,7 @@ class PropertyExtract extends DefaultXmlStep { meta = Some(metadata) } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) consumer.receive("result", doc.get, meta.get) diff --git a/src/main/scala/com/xmlcalabash/steps/PropertyMerge.scala b/src/main/scala/com/xmlcalabash/steps/PropertyMerge.scala index 18dfcfa..1b9a991 100644 --- a/src/main/scala/com/xmlcalabash/steps/PropertyMerge.scala +++ b/src/main/scala/com/xmlcalabash/steps/PropertyMerge.scala @@ -3,9 +3,9 @@ package com.xmlcalabash.steps import com.jafpl.steps.PortCardinality import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} -import com.xmlcalabash.model.xml.XMLContext +import com.xmlcalabash.model.xxml.{XMLStaticContext, XStaticContext} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.S9Api +import com.xmlcalabash.util.{MinimalStaticContext, S9Api} import net.sf.saxon.s9api.{Axis, QName, XdmAtomicValue, XdmNode, XdmNodeKind, XdmValue} import scala.collection.mutable @@ -32,7 +32,7 @@ class PropertyMerge extends DefaultXmlStep { } } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) propDoc.get match { @@ -62,7 +62,7 @@ class PropertyMerge extends DefaultXmlStep { consumer.receive("result", sourceDoc.get, newmeta) } - private def extractProperties(context: StaticContext, node: XdmNode): Map[QName,XdmValue] = { + private def extractProperties(context: MinimalStaticContext, node: XdmNode): Map[QName,XdmValue] = { val prop = mutable.HashMap.empty[QName,XdmValue] val piter = node.axisIterator(Axis.CHILD) @@ -77,13 +77,8 @@ class PropertyMerge extends DefaultXmlStep { val vtypestr = Option(pnode.getAttributeValue(XProcConstants.xsi_type)) val vtype = if (vtypestr.isDefined) { - val ns = S9Api.inScopeNamespaces(pnode) - val scontext = new XMLContext(config.config) - scontext.nsBindings = ns - if (location.isDefined) { - scontext.location = location.get - } - Some(ValueParser.parseQName(vtypestr.get, scontext)) + val scontext = new XStaticContext(location, S9Api.inScopeNamespaces(pnode)) + Some(scontext.parseQName(vtypestr.get)) } else { None } @@ -139,12 +134,8 @@ class PropertyMerge extends DefaultXmlStep { case XProcConstants.xs_anyURI => prop.put(name, new XdmAtomicValue(strvalue)) case XProcConstants.xs_QName => - val scontext = new XMLContext(config.config) - scontext.nsBindings = S9Api.inScopeNamespaces(node) - if (location.isDefined) { - scontext.location = location.get - } - prop.put(name, new XdmAtomicValue(ValueParser.parseQName(strvalue,scontext))) + val scontext = new XStaticContext(location, S9Api.inScopeNamespaces(node)) + prop.put(name, new XdmAtomicValue(scontext.parseQName(strvalue))) case XProcConstants.xs_notation => prop.put(name, new XdmAtomicValue(strvalue)) case XProcConstants.xs_decimal => diff --git a/src/main/scala/com/xmlcalabash/steps/Rename.scala b/src/main/scala/com/xmlcalabash/steps/Rename.scala index 75ec524..9a22e8b 100644 --- a/src/main/scala/com/xmlcalabash/steps/Rename.scala +++ b/src/main/scala/com/xmlcalabash/steps/Rename.scala @@ -4,7 +4,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{XdmNodeItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime._ -import com.xmlcalabash.util.TypeUtils +import com.xmlcalabash.util.{MinimalStaticContext, TypeUtils} import net.sf.saxon.om.{AttributeInfo, AttributeMap, EmptyAttributeMap, FingerprintedQName} import net.sf.saxon.s9api.{Axis, QName, XdmAtomicValue, XdmNode} import net.sf.saxon.value.QNameValue @@ -14,7 +14,7 @@ import scala.jdk.CollectionConverters.ListHasAsScala class Rename() extends DefaultXmlStep with ProcessMatchingNodes { private val _new_name = new QName("new-name") - private var context: StaticContext = _ + private var context: MinimalStaticContext = _ private var newName: QName = _ private var pattern: String = _ private var matcher: ProcessMatch = _ @@ -29,7 +29,7 @@ class Rename() extends DefaultXmlStep with ProcessMatchingNodes { metadata = meta } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) newName = qnameBinding(_new_name).get diff --git a/src/main/scala/com/xmlcalabash/steps/Replace.scala b/src/main/scala/com/xmlcalabash/steps/Replace.scala index f44c746..54c4c77 100644 --- a/src/main/scala/com/xmlcalabash/steps/Replace.scala +++ b/src/main/scala/com/xmlcalabash/steps/Replace.scala @@ -4,7 +4,7 @@ import com.jafpl.steps.PortCardinality import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.S9Api +import com.xmlcalabash.util.{MinimalStaticContext, S9Api} import net.sf.saxon.om.AttributeMap import net.sf.saxon.s9api.{Axis, QName, XdmAtomicValue, XdmNode, XdmNodeKind} @@ -34,7 +34,7 @@ class Replace() extends DefaultXmlStep with ProcessMatchingNodes { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) pattern = stringBinding(XProcConstants._match) diff --git a/src/main/scala/com/xmlcalabash/steps/SetAttributes.scala b/src/main/scala/com/xmlcalabash/steps/SetAttributes.scala index c9da1eb..448dfed 100644 --- a/src/main/scala/com/xmlcalabash/steps/SetAttributes.scala +++ b/src/main/scala/com/xmlcalabash/steps/SetAttributes.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{S9Api, TypeUtils} +import com.xmlcalabash.util.{MinimalStaticContext, S9Api, TypeUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap, NamespaceMap} import net.sf.saxon.s9api.{Axis, QName, XdmNode} @@ -31,7 +31,7 @@ class SetAttributes() extends DefaultXmlStep with ProcessMatchingNodes { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) pattern = stringBinding(XProcConstants._match) diff --git a/src/main/scala/com/xmlcalabash/steps/SetProperties.scala b/src/main/scala/com/xmlcalabash/steps/SetProperties.scala index be8ebf8..c2aca25 100644 --- a/src/main/scala/com/xmlcalabash/steps/SetProperties.scala +++ b/src/main/scala/com/xmlcalabash/steps/SetProperties.scala @@ -4,8 +4,10 @@ import java.net.URI import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} +import com.xmlcalabash.util.MinimalStaticContext import com.xmlcalabash.util.TypeUtils.castAsXml import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmMap, XdmNode, XdmValue} +import org.apache.xerces.util.URI.MalformedURIException import scala.collection.mutable import scala.jdk.CollectionConverters.MapHasAsScala @@ -28,7 +30,7 @@ class SetProperties() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val properties = mapBinding(XProcConstants._properties) @@ -78,12 +80,19 @@ class SetProperties() extends DefaultXmlStep { var result = source // The base URI is special; make sure it's not some bogus string if (newprops.contains(XProcConstants._base_uri)) { - val uri = if (context.baseURI.isDefined) { - resolveURI(context.baseURI.get, newprops(XProcConstants._base_uri).toString) - } else { - new URI(newprops(XProcConstants._base_uri).toString) + val ustr = newprops(XProcConstants._base_uri).toString + try { + val uri = new URI(ustr) + if (!uri.isAbsolute) { + throw XProcException.xdInvalidURI(ustr, location) + } + newprops.put(XProcConstants._base_uri, new XdmAtomicValue(uri)) + } catch { + case _: MalformedURIException => + throw XProcException.xdInvalidURI(ustr, location) + case ex: Exception => + throw ex } - newprops.put(XProcConstants._base_uri, new XdmAtomicValue(uri)) } else { if (metadata.properties.contains(XProcConstants._base_uri)) { // The base URI property has been removed... diff --git a/src/main/scala/com/xmlcalabash/steps/SplitSequence.scala b/src/main/scala/com/xmlcalabash/steps/SplitSequence.scala index 12e4c43..703bb9a 100644 --- a/src/main/scala/com/xmlcalabash/steps/SplitSequence.scala +++ b/src/main/scala/com/xmlcalabash/steps/SplitSequence.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.jafpl.steps.PortCardinality import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{BinaryNode, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.S9Api +import com.xmlcalabash.util.{MinimalStaticContext, S9Api} import net.sf.saxon.expr.LastPositionFinder import net.sf.saxon.om.{FocusIterator, Item, NodeInfo} import net.sf.saxon.s9api.{QName, XdmItem, XdmMap, XdmNode} @@ -31,7 +31,7 @@ class SplitSequence() extends DefaultXmlStep { metas += metadata } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) val initialOnly = bindings(_initial_only).value.getUnderlyingValue.effectiveBooleanValue() @@ -49,7 +49,7 @@ class SplitSequence() extends DefaultXmlStep { if (staticContext.baseURI.isDefined) { compiler.setBaseURI(staticContext.baseURI.get) } - for ((pfx, uri) <- bindings(XProcConstants._test).context.nsBindings) { + for ((pfx, uri) <- bindings(XProcConstants._test).context.inscopeNamespaces) { compiler.declareNamespace(pfx, uri) } val exec = compiler.compile(testExpr) diff --git a/src/main/scala/com/xmlcalabash/steps/Store.scala b/src/main/scala/com/xmlcalabash/steps/Store.scala index ea4e667..859f696 100644 --- a/src/main/scala/com/xmlcalabash/steps/Store.scala +++ b/src/main/scala/com/xmlcalabash/steps/Store.scala @@ -2,12 +2,11 @@ package com.xmlcalabash.steps import java.io.{FileOutputStream, InputStream} import java.net.URI - import com.jafpl.steps.PortCardinality import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, S9Api} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api} import net.sf.saxon.s9api.{Serializer, XdmNode} class Store extends DefaultXmlStep { @@ -26,7 +25,7 @@ class Store extends DefaultXmlStep { smeta = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val href = if (context.baseURI.isDefined) { diff --git a/src/main/scala/com/xmlcalabash/steps/StringReplace.scala b/src/main/scala/com/xmlcalabash/steps/StringReplace.scala index 8306a62..eccc9a1 100644 --- a/src/main/scala/com/xmlcalabash/steps/StringReplace.scala +++ b/src/main/scala/com/xmlcalabash/steps/StringReplace.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.jafpl.steps.PortCardinality import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{S9Api, TypeUtils} +import com.xmlcalabash.util.{MinimalStaticContext, S9Api, TypeUtils} import net.sf.saxon.`type`.BuiltInAtomicType import net.sf.saxon.event.ReceiverOption import net.sf.saxon.om.{AttributeInfo, AttributeMap, EmptyAttributeMap} @@ -18,7 +18,7 @@ class StringReplace() extends DefaultXmlStep with ProcessMatchingNodes { private var pattern: String = _ private var matcher: ProcessMatch = _ private var replace: String = _ - private var replContext: StaticContext = _ + private var replContext: MinimalStaticContext = _ override def inputSpec: XmlPortSpecification = new XmlPortSpecification( Map("source"->PortCardinality.EXACTLY_ONE), @@ -33,7 +33,7 @@ class StringReplace() extends DefaultXmlStep with ProcessMatchingNodes { source_metadata = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) pattern = stringBinding(XProcConstants._match) @@ -52,7 +52,7 @@ class StringReplace() extends DefaultXmlStep with ProcessMatchingNodes { if (replContext.baseURI.isDefined) { compiler.setBaseURI(replContext.baseURI.get) } - for ((pfx, uri) <- replContext.nsBindings) { + for ((pfx, uri) <- replContext.inscopeNamespaces) { compiler.declareNamespace(pfx, uri) } val expr = compiler.compile(replace) diff --git a/src/main/scala/com/xmlcalabash/steps/Unarchive.scala b/src/main/scala/com/xmlcalabash/steps/Unarchive.scala index 5d86416..4e17cda 100644 --- a/src/main/scala/com/xmlcalabash/steps/Unarchive.scala +++ b/src/main/scala/com/xmlcalabash/steps/Unarchive.scala @@ -7,7 +7,7 @@ import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{ValueParser, XProcConstants} import com.xmlcalabash.runtime.{BinaryNode, NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, URIUtils, Urify} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, URIUtils, Urify} import net.sf.saxon.s9api.{QName, XdmArray, XdmValue} import org.apache.commons.compress.archivers.zip.ZipFile import org.apache.commons.compress.utils.IOUtils @@ -44,7 +44,7 @@ class Unarchive extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) format = if (qnameBinding(XProcConstants._format).isDefined) { @@ -98,7 +98,7 @@ class Unarchive extends DefaultXmlStep { } } - private def unzip(context: StaticContext): Unit = { + private def unzip(context: MinimalStaticContext): Unit = { // ZIP requires random access: https://commons.apache.org/proper/commons-compress/zip.html source match { case bn: BinaryNode => @@ -108,7 +108,7 @@ class Unarchive extends DefaultXmlStep { } } - private def unzipFile(context: StaticContext, zfile: File): Unit = { + private def unzipFile(context: MinimalStaticContext, zfile: File): Unit = { val zipIn = new ZipFile(zfile) val enum = zipIn.getEntries while (enum.hasMoreElements) { diff --git a/src/main/scala/com/xmlcalabash/steps/Uncompress.scala b/src/main/scala/com/xmlcalabash/steps/Uncompress.scala index 5de9e0a..ecf8d2b 100644 --- a/src/main/scala/com/xmlcalabash/steps/Uncompress.scala +++ b/src/main/scala/com/xmlcalabash/steps/Uncompress.scala @@ -6,7 +6,7 @@ import com.xmlcalabash.config.{DocumentRequest, DocumentResponse} import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{ValueParser, XProcConstants} import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmValue} import scala.collection.mutable @@ -36,7 +36,7 @@ class Uncompress extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) format = qnameBinding(XProcConstants._format) diff --git a/src/main/scala/com/xmlcalabash/steps/UnescapeMarkup.scala b/src/main/scala/com/xmlcalabash/steps/UnescapeMarkup.scala index cb77982..3e5a13f 100644 --- a/src/main/scala/com/xmlcalabash/steps/UnescapeMarkup.scala +++ b/src/main/scala/com/xmlcalabash/steps/UnescapeMarkup.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, S9Api} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api} import net.sf.saxon.om.FingerprintedQName import net.sf.saxon.s9api._ @@ -24,7 +24,7 @@ class UnescapeMarkup() extends DefaultXmlStep { metadata = meta } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) if (definedBinding(XProcConstants._namespace)) { diff --git a/src/main/scala/com/xmlcalabash/steps/Unwrap.scala b/src/main/scala/com/xmlcalabash/steps/Unwrap.scala index 855c559..3ccac36 100644 --- a/src/main/scala/com/xmlcalabash/steps/Unwrap.scala +++ b/src/main/scala/com/xmlcalabash/steps/Unwrap.scala @@ -3,6 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.om.AttributeMap import net.sf.saxon.s9api.{Axis, XdmNode, XdmNodeKind} @@ -20,7 +21,7 @@ class Unwrap() extends DefaultXmlStep with ProcessMatchingNodes { source_metadata = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) pattern = stringBinding(XProcConstants._match) diff --git a/src/main/scala/com/xmlcalabash/steps/Uuid.scala b/src/main/scala/com/xmlcalabash/steps/Uuid.scala index 0afc6ac..cfc9c1d 100644 --- a/src/main/scala/com/xmlcalabash/steps/Uuid.scala +++ b/src/main/scala/com/xmlcalabash/steps/Uuid.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{HashUtils, TypeUtils} +import com.xmlcalabash.util.{HashUtils, MinimalStaticContext, TypeUtils} import net.sf.saxon.`type`.BuiltInAtomicType import net.sf.saxon.event.ReceiverOption import net.sf.saxon.om.{AttributeInfo, AttributeMap, EmptyAttributeMap} @@ -26,7 +26,7 @@ class Uuid() extends DefaultXmlStep with ProcessMatchingNodes { this.metadata = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val version = integerBinding(XProcConstants._version) diff --git a/src/main/scala/com/xmlcalabash/steps/ValidateWithRNG.scala b/src/main/scala/com/xmlcalabash/steps/ValidateWithRNG.scala index 1ce7d36..75e1a2d 100644 --- a/src/main/scala/com/xmlcalabash/steps/ValidateWithRNG.scala +++ b/src/main/scala/com/xmlcalabash/steps/ValidateWithRNG.scala @@ -1,7 +1,6 @@ package com.xmlcalabash.steps import java.io.{IOException, StringReader} - import com.jafpl.graph.Location import com.jafpl.steps.PortCardinality import com.thaiopensource.util.PropertyMapBuilder @@ -12,7 +11,7 @@ import com.thaiopensource.validate.{SchemaReader, ValidateProperty, ValidationDr import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.runtime.{StaticContext, XProcLocation, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.util.xc.Errors -import com.xmlcalabash.util.{CachingErrorListener, S9Api} +import com.xmlcalabash.util.{CachingErrorListener, MinimalStaticContext, S9Api} import net.sf.saxon.s9api.{QName, XdmNode} import org.xml.sax.InputSource @@ -60,7 +59,7 @@ class ValidateWithRNG() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) if (definedBinding(_dtd_id_idref_warnings)) { @@ -136,7 +135,7 @@ class ValidateWithRNG() extends DefaultXmlStep { } } } else { - throw XProcException.xcNotSchemaValidRelaxNG(source.getBaseURI.toASCIIString, "Error loading schema", location) + throw XProcException.xcNotRelaxNG(source.getBaseURI.toASCIIString, "Error loading schema", location) } consumer.receive("report", report.endErrors() , XProcMetadata.XML) diff --git a/src/main/scala/com/xmlcalabash/steps/ValidateWithSCH.scala b/src/main/scala/com/xmlcalabash/steps/ValidateWithSCH.scala index acd46d1..260546d 100644 --- a/src/main/scala/com/xmlcalabash/steps/ValidateWithSCH.scala +++ b/src/main/scala/com/xmlcalabash/steps/ValidateWithSCH.scala @@ -7,7 +7,7 @@ import com.jafpl.steps.PortCardinality import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{StaticContext, XProcLocation, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, S9Api, SchematronImpl} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api, SchematronImpl} import javax.xml.transform.sax.SAXSource import javax.xml.transform.{Source, URIResolver} @@ -54,7 +54,7 @@ class ValidateWithSCH() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) assert_valid = booleanBinding(XProcConstants._assert_valid).getOrElse(assert_valid) diff --git a/src/main/scala/com/xmlcalabash/steps/ValidateWithXSD.scala b/src/main/scala/com/xmlcalabash/steps/ValidateWithXSD.scala index 5480905..049088f 100644 --- a/src/main/scala/com/xmlcalabash/steps/ValidateWithXSD.scala +++ b/src/main/scala/com/xmlcalabash/steps/ValidateWithXSD.scala @@ -1,14 +1,14 @@ package com.xmlcalabash.steps import java.net.URI - import com.jafpl.steps.PortCardinality import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.util.xc.Errors -import com.xmlcalabash.util.{CachingErrorListener, MediaType, S9Api} +import com.xmlcalabash.util.{CachingErrorListener, MediaType, MinimalStaticContext, S9Api} + import javax.xml.transform.sax.SAXSource import net.sf.saxon.Controller import net.sf.saxon.`type`.ValidationException @@ -60,7 +60,7 @@ class ValidateWithXSD() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val manager = Option(config.processor.getSchemaManager) diff --git a/src/main/scala/com/xmlcalabash/steps/Wrap.scala b/src/main/scala/com/xmlcalabash/steps/Wrap.scala index 181744d..c74b780 100644 --- a/src/main/scala/com/xmlcalabash/steps/Wrap.scala +++ b/src/main/scala/com/xmlcalabash/steps/Wrap.scala @@ -4,7 +4,7 @@ import com.jafpl.steps.PortCardinality import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{ProcessMatch, ProcessMatchingNodes, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, S9Api} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api} import net.sf.saxon.om.AttributeMap import net.sf.saxon.s9api.{Axis, QName, XdmItem, XdmNode, XdmNodeKind, XdmValue} @@ -19,10 +19,10 @@ class Wrap() extends DefaultXmlStep with ProcessMatchingNodes { private var pattern: String = _ private var matcher: ProcessMatch = _ private var groupAdjacent = Option.empty[String] - private var groupAdjacentContext = Option.empty[StaticContext] + private var groupAdjacentContext = Option.empty[MinimalStaticContext] private var wrapper: QName = _ private val inGroup = mutable.Stack[Boolean]() - private var staticContext: StaticContext = _ + private var staticContext: MinimalStaticContext = _ override def inputSpec: XmlPortSpecification = new XmlPortSpecification( Map("source"->PortCardinality.EXACTLY_ONE), @@ -35,7 +35,7 @@ class Wrap() extends DefaultXmlStep with ProcessMatchingNodes { source_metadata = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) staticContext = context @@ -161,7 +161,7 @@ class Wrap() extends DefaultXmlStep with ProcessMatchingNodes { if (staticContext.baseURI.isDefined) { xcomp.setBaseURI(staticContext.baseURI.get) } - for ((pfx,uri) <- groupAdjacentContext.get.nsBindings) { + for ((pfx,uri) <- groupAdjacentContext.get.inscopeNamespaces) { xcomp.declareNamespace(pfx, uri) } diff --git a/src/main/scala/com/xmlcalabash/steps/WrapSequence.scala b/src/main/scala/com/xmlcalabash/steps/WrapSequence.scala index be4ebbd..444540c 100644 --- a/src/main/scala/com/xmlcalabash/steps/WrapSequence.scala +++ b/src/main/scala/com/xmlcalabash/steps/WrapSequence.scala @@ -2,13 +2,11 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.SaxonTreeBuilder -import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.S9Api +import com.xmlcalabash.runtime.{XProcMetadata, XmlPortSpecification} +import com.xmlcalabash.util.{MinimalStaticContext, S9Api} import net.sf.saxon.expr.LastPositionFinder -import net.sf.saxon.om.{Item, NodeInfo} -import net.sf.saxon.s9api.{QName, XdmItem, XdmNode, XdmValue} +import net.sf.saxon.s9api.{QName, XdmNode, XdmValue} import net.sf.saxon.tree.iter.ManualIterator -import net.sf.saxon.value.SequenceExtent import scala.collection.mutable.ListBuffer @@ -18,7 +16,7 @@ class WrapSequence extends DefaultXmlStep { private val inputs = ListBuffer.empty[XdmNode] private var groupAdjacent = Option.empty[String] - private var groupAdjacentContext = Option.empty[StaticContext] + private var groupAdjacentContext = Option.empty[MinimalStaticContext] private var wrapper: QName = _ private var index = 1 @@ -34,7 +32,7 @@ class WrapSequence extends DefaultXmlStep { } } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) wrapper = qnameBinding(_wrapper).get @@ -50,7 +48,7 @@ class WrapSequence extends DefaultXmlStep { } } - def runSimple(staticContext: StaticContext): Unit = { + def runSimple(staticContext: MinimalStaticContext): Unit = { val builder = new SaxonTreeBuilder(config) builder.startDocument(staticContext.baseURI) @@ -64,7 +62,7 @@ class WrapSequence extends DefaultXmlStep { consumer.receive("result", builder.result, XProcMetadata.XML) } - def runAdjacent(staticContext: StaticContext): Unit = { + def runAdjacent(staticContext: MinimalStaticContext): Unit = { var inGroup = false var lastValue: XdmValue = null var builder: SaxonTreeBuilder = null @@ -111,7 +109,7 @@ class WrapSequence extends DefaultXmlStep { private def adjacentValue(node: XdmNode): XdmValue = { val compiler = config.processor.newXPathCompiler() compiler.setBaseURI(groupAdjacentContext.get.baseURI.get) - for ((pfx, uri) <- bindings(_group_adjacent).context.nsBindings) { + for ((pfx, uri) <- bindings(_group_adjacent).context.inscopeNamespaces) { compiler.declareNamespace(pfx, uri) } val exec = compiler.compile(groupAdjacent.get) diff --git a/src/main/scala/com/xmlcalabash/steps/WwwFormUrlDecode.scala b/src/main/scala/com/xmlcalabash/steps/WwwFormUrlDecode.scala index 3eefdac..5ec4a1d 100644 --- a/src/main/scala/com/xmlcalabash/steps/WwwFormUrlDecode.scala +++ b/src/main/scala/com/xmlcalabash/steps/WwwFormUrlDecode.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{XdmAtomicValue, XdmMap, XdmValue} import java.net.URLDecoder @@ -15,7 +15,7 @@ class WwwFormUrlDecode() extends DefaultXmlStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.JSONRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val value = stringBinding(XProcConstants._value).trim diff --git a/src/main/scala/com/xmlcalabash/steps/WwwFormUrlEncode.scala b/src/main/scala/com/xmlcalabash/steps/WwwFormUrlEncode.scala index d3d5e35..a0e8fd1 100644 --- a/src/main/scala/com/xmlcalabash/steps/WwwFormUrlEncode.scala +++ b/src/main/scala/com/xmlcalabash/steps/WwwFormUrlEncode.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{XdmAtomicValue, XdmMap, XdmValue} import java.net.URLDecoder @@ -16,7 +16,7 @@ class WwwFormUrlEncode() extends DefaultXmlStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.TEXTRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val parameters = mapBinding(XProcConstants._parameters) diff --git a/src/main/scala/com/xmlcalabash/steps/XInclude.scala b/src/main/scala/com/xmlcalabash/steps/XInclude.scala index a651bbc..938afff 100644 --- a/src/main/scala/com/xmlcalabash/steps/XInclude.scala +++ b/src/main/scala/com/xmlcalabash/steps/XInclude.scala @@ -4,6 +4,7 @@ import com.nwalsh.sinclude.exceptions.{MalformedXPointerSchemeException, Unknown import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{QName, XdmNode} import net.sf.saxon.trans.XPathException @@ -17,7 +18,7 @@ class XInclude() extends DefaultXmlStep { private var source: XdmNode = _ private var smeta: XProcMetadata = _ - private var staticContext: StaticContext = _ + private var staticContext: MinimalStaticContext = _ override def inputSpec: XmlPortSpecification = XmlPortSpecification.MARKUPSOURCE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT @@ -27,7 +28,7 @@ class XInclude() extends DefaultXmlStep { smeta = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) staticContext = context diff --git a/src/main/scala/com/xmlcalabash/steps/XQuery.scala b/src/main/scala/com/xmlcalabash/steps/XQuery.scala index 6eba100..98eeee4 100644 --- a/src/main/scala/com/xmlcalabash/steps/XQuery.scala +++ b/src/main/scala/com/xmlcalabash/steps/XQuery.scala @@ -6,7 +6,7 @@ import com.jafpl.steps.PortCardinality import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, S9Api, XProcCollectionFinder} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api, XProcCollectionFinder} import javax.xml.transform.{ErrorListener, TransformerException} import net.sf.saxon.event.{PipelineConfiguration, Receiver} @@ -58,7 +58,7 @@ class XQuery extends DefaultXmlStep { } } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) val pmap = mapBinding(XProcConstants._parameters) diff --git a/src/main/scala/com/xmlcalabash/steps/Xslt.scala b/src/main/scala/com/xmlcalabash/steps/Xslt.scala index 79aa58e..d7eb348 100644 --- a/src/main/scala/com/xmlcalabash/steps/Xslt.scala +++ b/src/main/scala/com/xmlcalabash/steps/Xslt.scala @@ -5,7 +5,7 @@ import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} import com.xmlcalabash.runtime.{BinaryNode, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{MediaType, PipelineEnvironmentOptionString, S9Api, URIUtils, Urify, ValueUtils, XProcCollectionFinder, Xslt10ClassLoader, Xslt10Source} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, PipelineEnvironmentOptionString, S9Api, URIUtils, Urify, ValueUtils, XProcCollectionFinder, Xslt10ClassLoader, Xslt10Source} import net.sf.saxon.Configuration import net.sf.saxon.event.{PipelineConfiguration, Receiver} import net.sf.saxon.expr.XPathContext @@ -36,7 +36,7 @@ class Xslt extends DefaultXmlStep { private val inputSequence = ListBuffer.empty[XdmItem] private val inputMetadata = ListBuffer.empty[XProcMetadata] - private var staticContext: StaticContext = _ + private var staticContext: MinimalStaticContext = _ private var globalContextItem = Option.empty[XdmValue] private var initialMode = Option.empty[QName] private var templateName = Option.empty[QName] @@ -82,7 +82,7 @@ class Xslt extends DefaultXmlStep { } } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) this.staticContext = staticContext @@ -98,9 +98,6 @@ class Xslt extends DefaultXmlStep { if (bindings.contains(_global_context_item)) { globalContextItem = Some(bindings(_global_context_item).value) - if (globalContextItem.get.size() == 0) { - globalContextItem = None - } } initialMode = qnameBinding(_initial_mode) @@ -287,7 +284,9 @@ class Xslt extends DefaultXmlStep { // FIXME: transformer.getUnderlyingController().setUnparsedTextURIResolver(unparsedTextURIResolver) if (globalContextItem.isDefined) { - transformer.setGlobalContextItem(globalContextItem.get.asInstanceOf[XdmItem]) + if (globalContextItem.get.size() > 0) { + transformer.setGlobalContextItem(globalContextItem.get.asInstanceOf[XdmItem]) + } } try { diff --git a/src/main/scala/com/xmlcalabash/steps/file/DirectoryList.scala b/src/main/scala/com/xmlcalabash/steps/file/DirectoryList.scala index 1e64902..b3fee4c 100644 --- a/src/main/scala/com/xmlcalabash/steps/file/DirectoryList.scala +++ b/src/main/scala/com/xmlcalabash/steps/file/DirectoryList.scala @@ -4,7 +4,7 @@ import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep import com.xmlcalabash.util.stores.{DataInfo, FallbackDataStore, FileDataStore} -import com.xmlcalabash.util.{MediaType, TypeUtils, URIUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, TypeUtils, URIUtils} import net.sf.saxon.om.{AttributeMap, SingletonAttributeMap} import net.sf.saxon.s9api.{QName, XdmAtomicValue} @@ -21,7 +21,7 @@ class DirectoryList() extends DefaultXmlStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val builder = new SaxonTreeBuilder(config) diff --git a/src/main/scala/com/xmlcalabash/steps/file/FileCopy.scala b/src/main/scala/com/xmlcalabash/steps/file/FileCopy.scala index d7ddfee..a809db8 100644 --- a/src/main/scala/com/xmlcalabash/steps/file/FileCopy.scala +++ b/src/main/scala/com/xmlcalabash/steps/file/FileCopy.scala @@ -4,7 +4,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.util.stores.{DataInfo, DataReader, DataWriter} -import com.xmlcalabash.util.{InternetProtocolRequest, MediaType} +import com.xmlcalabash.util.{InternetProtocolRequest, MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmAtomicValue} import java.io.{InputStream, OutputStream} @@ -23,7 +23,7 @@ class FileCopy() extends FileStep { private var copyLinks = false private var copyAttributes = false - private var staticContext: StaticContext = _ + private var staticContext: MinimalStaticContext = _ private var href: URI = _ private var target: URI = _ private var exception = Option.empty[Exception] @@ -33,7 +33,7 @@ class FileCopy() extends FileStep { override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) staticContext = context diff --git a/src/main/scala/com/xmlcalabash/steps/file/FileCreateTempFile.scala b/src/main/scala/com/xmlcalabash/steps/file/FileCreateTempFile.scala index 218a1d2..4f37be9 100644 --- a/src/main/scala/com/xmlcalabash/steps/file/FileCreateTempFile.scala +++ b/src/main/scala/com/xmlcalabash/steps/file/FileCreateTempFile.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps.file import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import java.net.URI import java.nio.file.{Files, Path, Paths, StandardCopyOption} @@ -15,7 +15,7 @@ class FileCreateTempFile() extends FileStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val href = uriBinding(XProcConstants._href) diff --git a/src/main/scala/com/xmlcalabash/steps/file/FileDelete.scala b/src/main/scala/com/xmlcalabash/steps/file/FileDelete.scala index 8d680a8..49c5a90 100644 --- a/src/main/scala/com/xmlcalabash/steps/file/FileDelete.scala +++ b/src/main/scala/com/xmlcalabash/steps/file/FileDelete.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps.file import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{InternetProtocolRequest, MediaType} +import com.xmlcalabash.util.{InternetProtocolRequest, MediaType, MinimalStaticContext} import java.io.IOException import java.net.URI @@ -16,14 +16,14 @@ class FileDelete() extends FileStep { private var recursive = false private var failOnError = true - private var staticContext: StaticContext = _ + private var staticContext: MinimalStaticContext = _ private var exception = Option.empty[Exception] private val failures = ListBuffer.empty[URI] override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) staticContext = context diff --git a/src/main/scala/com/xmlcalabash/steps/file/FileInfo.scala b/src/main/scala/com/xmlcalabash/steps/file/FileInfo.scala index 84c0d79..f550071 100644 --- a/src/main/scala/com/xmlcalabash/steps/file/FileInfo.scala +++ b/src/main/scala/com/xmlcalabash/steps/file/FileInfo.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps.file import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.{InternetProtocolRequest, MediaType, TypeUtils, URIUtils} +import com.xmlcalabash.util.{InternetProtocolRequest, MediaType, MinimalStaticContext, TypeUtils, URIUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} import net.sf.saxon.s9api.QName @@ -17,13 +17,13 @@ class FileInfo() extends FileStep { private var href: URI = _ private var failOnError = true - private var staticContext: StaticContext = _ + private var staticContext: MinimalStaticContext = _ private var builder: SaxonTreeBuilder = _ override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) staticContext = context diff --git a/src/main/scala/com/xmlcalabash/steps/file/FileMkdir.scala b/src/main/scala/com/xmlcalabash/steps/file/FileMkdir.scala index ad4f4ce..04afc20 100644 --- a/src/main/scala/com/xmlcalabash/steps/file/FileMkdir.scala +++ b/src/main/scala/com/xmlcalabash/steps/file/FileMkdir.scala @@ -4,7 +4,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.{InternetProtocolRequest, MediaType, URIUtils} +import com.xmlcalabash.util.{InternetProtocolRequest, MediaType, MinimalStaticContext, URIUtils} import java.io.IOException import java.net.URI @@ -18,7 +18,7 @@ class FileMkdir() extends FileStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) href = uriBinding(XProcConstants._href).get diff --git a/src/main/scala/com/xmlcalabash/steps/file/FileMove.scala b/src/main/scala/com/xmlcalabash/steps/file/FileMove.scala index c7ade19..cea3636 100644 --- a/src/main/scala/com/xmlcalabash/steps/file/FileMove.scala +++ b/src/main/scala/com/xmlcalabash/steps/file/FileMove.scala @@ -4,7 +4,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.QName import java.net.URI @@ -16,7 +16,7 @@ class FileMove() extends FileStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val href = uriBinding(XProcConstants._href).get diff --git a/src/main/scala/com/xmlcalabash/steps/file/FileTouch.scala b/src/main/scala/com/xmlcalabash/steps/file/FileTouch.scala index 3a9a9b6..86ee9ba 100644 --- a/src/main/scala/com/xmlcalabash/steps/file/FileTouch.scala +++ b/src/main/scala/com/xmlcalabash/steps/file/FileTouch.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps.file import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import java.io.FileOutputStream import java.nio.file.{Files, Path, Paths} @@ -15,7 +15,7 @@ class FileTouch() extends FileStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val href = uriBinding(XProcConstants._href).get diff --git a/src/main/scala/com/xmlcalabash/steps/internal/AbstractLoader.scala b/src/main/scala/com/xmlcalabash/steps/internal/AbstractLoader.scala index 738a9e3..4ea9a98 100644 --- a/src/main/scala/com/xmlcalabash/steps/internal/AbstractLoader.scala +++ b/src/main/scala/com/xmlcalabash/steps/internal/AbstractLoader.scala @@ -3,9 +3,10 @@ package com.xmlcalabash.steps.internal import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{AnyItemMessage, XProcItemMessage, XdmNodeItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} +import com.xmlcalabash.model.xxml.XStaticContext import com.xmlcalabash.runtime.{BinaryNode, DynamicContext, NameValueBinding, StaticContext, XProcExpression, XProcMetadata, XProcXPathExpression} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.{MediaType, S9Api} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api} import net.sf.saxon.s9api.{QName, XdmItem, XdmMap, XdmNode, XdmValue} import scala.collection.mutable @@ -16,11 +17,11 @@ class AbstractLoader() extends DefaultXmlStep { protected var contextItem = Option.empty[XProcItemMessage] protected var msgBindings = mutable.HashMap.empty[String, XProcItemMessage] protected var docProps = Map.empty[QName, XdmValue] - protected var exprContext: StaticContext = _ + protected var exprContext: MinimalStaticContext = _ protected var contentType: MediaType = _ override def receive(port: String, item: Any, meta: XProcMetadata): Unit = { - val context = new StaticContext(config, None) + val context = new XStaticContext() item match { case node: XdmNode => contextItem = Some(new XdmNodeItemMessage(node, meta, context)) @@ -37,7 +38,7 @@ class AbstractLoader() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) if (bindings.contains(XProcConstants._content_type)) { @@ -45,11 +46,13 @@ class AbstractLoader() extends DefaultXmlStep { } // Fake the statics + /* FIXME: for ((name,message) <- exprContext.statics) { val msg = message.asInstanceOf[XdmValueItemMessage] val qname = ValueParser.parseClarkName(name) receiveBinding(new NameValueBinding(qname, msg)) } + */ for ((name, binding) <- bindings) { binding.value match { @@ -75,9 +78,8 @@ class AbstractLoader() extends DefaultXmlStep { } protected def xpathValue(expr: XProcExpression): XdmValue = { - val dynContext = new DynamicContext() val eval = config.expressionEvaluator.newInstance() - val msg = eval.withContext(dynContext) { eval.singletonValue(expr, contextItem.toList, msgBindings.toMap, None) } + val msg = eval.singletonValue(expr, contextItem.toList, msgBindings.toMap, None) msg.item } } diff --git a/src/main/scala/com/xmlcalabash/steps/internal/ContentTypeChecker.scala b/src/main/scala/com/xmlcalabash/steps/internal/ContentTypeChecker.scala index 3e4e9af..dba4f07 100644 --- a/src/main/scala/com/xmlcalabash/steps/internal/ContentTypeChecker.scala +++ b/src/main/scala/com/xmlcalabash/steps/internal/ContentTypeChecker.scala @@ -8,9 +8,10 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.{XdmNodeItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.model.xxml.XStaticContext import com.xmlcalabash.runtime.params.ContentTypeCheckerParams import com.xmlcalabash.runtime.{ImplParams, NameValueBinding, StaticContext, XMLCalabashRuntime, XProcDataConsumer, XProcMetadata, XProcXPathExpression, XmlPortSpecification, XmlStep} -import com.xmlcalabash.util.{MediaType, S9Api, XProcVarValue} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api, XProcVarValue} import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmItem, XdmNode, XdmNodeKind, XdmValue} import org.slf4j.{Logger, LoggerFactory} @@ -34,9 +35,9 @@ class ContentTypeChecker() extends XmlStep { private val nodeMeta = mutable.HashMap.empty[XdmNode, XProcMetadata] private val nodes = ListBuffer.empty[XdmNode] protected var allowedTypes = List.empty[MediaType] - protected var errCode = XProcException.err_xd0038 + protected var errCode: QName = XProcException.err_xd0038 protected var select = Option.empty[String] - protected var selectContext: StaticContext = _ + protected var selectContext: XStaticContext = _ protected var portName: String = _ protected var sequence = false protected var inputPort = false @@ -136,12 +137,12 @@ class ContentTypeChecker() extends XmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { // FIXME: do whatever logging DefaultXmlStep does. - for ((name, message) <- selectContext.statics) { - if (!bindings.contains(name)) { - bindings.put(name, message) + for ((name, message) <- selectContext.inscopeConstants) { + if (!bindings.contains(name.getClarkName)) { + bindings.put(name.getClarkName, message.constantValue.get) } } diff --git a/src/main/scala/com/xmlcalabash/steps/internal/DocumentLoader.scala b/src/main/scala/com/xmlcalabash/steps/internal/DocumentLoader.scala index 0d35c4b..e40ac4a 100644 --- a/src/main/scala/com/xmlcalabash/steps/internal/DocumentLoader.scala +++ b/src/main/scala/com/xmlcalabash/steps/internal/DocumentLoader.scala @@ -9,7 +9,7 @@ import com.xmlcalabash.model.util.{ValueParser, XProcConstants} import com.xmlcalabash.runtime.params.DocumentLoaderParams import com.xmlcalabash.runtime.{BinaryNode, ImplParams, StaticContext, XProcMetadata, XProcXPathExpression, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.{MediaType, S9Api} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api} import net.sf.saxon.s9api.{QName, SaxonApiException, XdmMap, XdmNode, XdmValue} import scala.collection.mutable.ListBuffer @@ -60,7 +60,7 @@ class DocumentLoader() extends AbstractLoader { // nop, we do it after we've computed the href attribute } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val parts = ListBuffer.empty[String] diff --git a/src/main/scala/com/xmlcalabash/steps/internal/EmptyLoader.scala b/src/main/scala/com/xmlcalabash/steps/internal/EmptyLoader.scala index 5f45d35..21306e1 100644 --- a/src/main/scala/com/xmlcalabash/steps/internal/EmptyLoader.scala +++ b/src/main/scala/com/xmlcalabash/steps/internal/EmptyLoader.scala @@ -4,6 +4,7 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.runtime.params.EmptyLoaderParams import com.xmlcalabash.runtime.{ImplParams, StaticContext, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.QName class EmptyLoader() extends AbstractLoader { @@ -31,7 +32,7 @@ class EmptyLoader() extends AbstractLoader { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) // Produce nothing. } diff --git a/src/main/scala/com/xmlcalabash/steps/internal/InlineExpander.scala b/src/main/scala/com/xmlcalabash/steps/internal/InlineExpander.scala index 9047a29..4302d89 100644 --- a/src/main/scala/com/xmlcalabash/steps/internal/InlineExpander.scala +++ b/src/main/scala/com/xmlcalabash/steps/internal/InlineExpander.scala @@ -5,11 +5,11 @@ import com.jafpl.messages.Message import com.xmlcalabash.XMLCalabash import com.xmlcalabash.config.DocumentRequest import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.messages.{XProcItemMessage, XdmNodeItemMessage, XdmValueItemMessage} +import com.xmlcalabash.messages.{XProcItemMessage, XdmValueItemMessage} import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} -import com.xmlcalabash.model.xml.Inline -import com.xmlcalabash.runtime.{BinaryNode, DynamicContext, StaticContext, XProcExpression, XProcMetadata, XProcVtExpression, XProcXPathExpression} -import com.xmlcalabash.util.{MediaType, TypeUtils} +import com.xmlcalabash.model.xxml.{XInline, XMLStaticContext} +import com.xmlcalabash.runtime.{BinaryNode, DynamicContext, XMLCalabashRuntime, XProcExpression, XProcMetadata, XProcVtExpression, XProcXPathExpression} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, TypeUtils} import net.sf.saxon.`type`.BuiltInAtomicType import net.sf.saxon.event.ReceiverOption import net.sf.saxon.om.{AttributeInfo, AttributeMap, EmptyAttributeMap, NamespaceMap} @@ -26,20 +26,32 @@ import scala.jdk.CollectionConverters._ // the case where this is a default binding, it must *not* evaluate its options if the default // is not used. -protected[xmlcalabash] class InlineExpander(val config: XMLCalabash, val node: XdmNode, val meta: XProcMetadata, val exprContext: StaticContext, val location: Option[Location]) { +protected[xmlcalabash] class InlineExpander(val config: XMLCalabash, val node: XdmNode, val meta: XProcMetadata, val exprContext: MinimalStaticContext, val location: Option[Location]) { + private var _documentProperties: Map[QName, XdmValue] = Map.empty[QName, XdmValue] + private val fq_inline_expand_text = TypeUtils.fqName(XProcConstants._inline_expand_text) + private val fq_p_inline_expand_text = TypeUtils.fqName(XProcConstants.p_inline_expand_text) + private val _msgBindings = mutable.HashMap.empty[String, XProcItemMessage] + var contentType: MediaType = meta.contentType var encoding: Option[String] = None var excludeURIs: Set[String] = Set() - var msgBindings: Map[String, XProcItemMessage] = Map() var contextItem: Option[XProcItemMessage] = None - private var _documentProperties: Map[QName, XdmValue] = Map.empty[QName, XdmValue] + def msgBindings: Map[String, XProcItemMessage] = _msgBindings.toMap + def msgBindings_=(bindings: Map[String, XProcItemMessage]): Unit = { + _msgBindings ++= bindings + } - private val fq_inline_expand_text = TypeUtils.fqName(XProcConstants._inline_expand_text) - private val fq_p_inline_expand_text = TypeUtils.fqName(XProcConstants.p_inline_expand_text) + def copyStaticOptionsToBindings(runtime: XMLCalabashRuntime): Unit = { + for ((name,value) <- runtime.staticOptions) { + if (exprContext.inscopeConstants.contains(name)) { + _msgBindings.put(name.getClarkName, value) + } + } + } - def this(inline: Inline) = { - this(inline.config, inline.node, new XProcMetadata(inline.contentType), inline.inlineContext, inline.location) + def this(inline: XInline) = { + this(inline.config, inline.content, new XProcMetadata(inline.contentType), inline.staticContext, inline.location) encoding = inline.encoding excludeURIs = inline.excludeURIs } @@ -313,9 +325,8 @@ protected[xmlcalabash] class InlineExpander(val config: XMLCalabash, val node: X } protected def xpathValue(expr: XProcExpression): XdmValue = { - val dynContext = new DynamicContext() val eval = config.expressionEvaluator.newInstance() - val msg = eval.withContext(dynContext) { eval.singletonValue(expr, contextItem.toList, msgBindings, None) } + val msg = eval.singletonValue(expr, contextItem.toList, msgBindings, None) msg.item } } diff --git a/src/main/scala/com/xmlcalabash/steps/internal/InlineLoader.scala b/src/main/scala/com/xmlcalabash/steps/internal/InlineLoader.scala index c96b643..e1de878 100644 --- a/src/main/scala/com/xmlcalabash/steps/internal/InlineLoader.scala +++ b/src/main/scala/com/xmlcalabash/steps/internal/InlineLoader.scala @@ -9,7 +9,7 @@ import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants import com.xmlcalabash.runtime.params.InlineLoaderParams import com.xmlcalabash.runtime.{BinaryNode, ImplParams, StaticContext, XProcMetadata, XProcVtExpression, XProcXPathExpression, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.{MediaType, S9Api, TypeUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, S9Api, TypeUtils} import net.sf.saxon.`type`.BuiltInAtomicType import net.sf.saxon.event.ReceiverOption import net.sf.saxon.om.{AttributeInfo, AttributeMap, EmptyAttributeMap, NamespaceMap} @@ -30,7 +30,7 @@ import scala.jdk.CollectionConverters._ class InlineLoader() extends AbstractLoader { private var node: XdmNode = _ private var encoding = Option.empty[String] - private var exclude_inline_prefixes = Option.empty[String] + private var excludeUris = Set.empty[String] private var expandText = false private var contextProvided = false @@ -54,7 +54,7 @@ class InlineLoader() extends AbstractLoader { content_type = doc.content_type encoding = doc.encoding _document_properties = doc.document_properties - exclude_inline_prefixes = doc.exclude_inline_prefixes + excludeUris = doc.exclude_uris expandText = doc.expand_text contextProvided = doc.context_provided exprContext = doc.context @@ -67,7 +67,7 @@ class InlineLoader() extends AbstractLoader { // nop, we do it after we've computed the href attribute } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val propContentType = if (docProps.contains(XProcConstants._content_type)) { @@ -123,14 +123,11 @@ class InlineLoader() extends AbstractLoader { val expander = new InlineExpander(config.config, node, meta, exprContext, location) expander.contentType = contentType expander.encoding = encoding - expander.excludeURIs = if (exclude_inline_prefixes.isDefined) { - S9Api.urisForPrefixes(node, exclude_inline_prefixes.get.split("\\s+").toSet) - } else { - Set() - } + expander.excludeURIs = excludeUris expander.msgBindings = msgBindings.toMap expander.contextItem = contextItem expander.documentProperties = docProps + expander.copyStaticOptionsToBindings(config) val req = expander.loadDocument(expandText) val resp = config.documentManager.parse(req) diff --git a/src/main/scala/com/xmlcalabash/steps/internal/SelectFilter.scala b/src/main/scala/com/xmlcalabash/steps/internal/SelectFilter.scala index 0a2333c..17a2c99 100644 --- a/src/main/scala/com/xmlcalabash/steps/internal/SelectFilter.scala +++ b/src/main/scala/com/xmlcalabash/steps/internal/SelectFilter.scala @@ -11,7 +11,7 @@ import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser} import com.xmlcalabash.runtime.params.SelectFilterParams import com.xmlcalabash.runtime.{BinaryNode, ImplParams, StaticContext, XMLCalabashRuntime, XProcDataConsumer, XProcMetadata, XProcXPathExpression, XmlPortSpecification, XmlStep} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.{MediaType, XProcVarValue} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, XProcVarValue} import net.sf.saxon.s9api.{QName, XdmItem, XdmNode, XdmNodeKind, XdmValue} import org.slf4j.{Logger, LoggerFactory} @@ -31,9 +31,8 @@ class SelectFilter() extends DefaultXmlStep { private val nodeMeta = mutable.HashMap.empty[Any, XProcMetadata] private val nodes = ListBuffer.empty[Any] private var select: String = _ - private var selectContext: StaticContext = _ + private var selectContext: MinimalStaticContext = _ private var port: String = _ - private var ispec: XmlPortSpecification = _ // ========================================================================== @@ -57,76 +56,44 @@ class SelectFilter() extends DefaultXmlStep { select = cp.select selectContext = cp.context port = cp.port - ispec = cp.ispec + sequence = cp.sequence case _ => throw XProcException.xiWrongImplParams() } } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) - msgBindings.clear() - msgBindings ++= selectContext.statics + for ((name, value) <- selectContext.inscopeConstants) { + msgBindings.put(name.getClarkName, value.constantValue.get) + } for ((name, binding) <- bindings) { msgBindings.put(name.getClarkName, new XdmValueItemMessage(binding.value, binding.meta, context)) } - if (nodes.isEmpty) { - makeSelection(List()) - } else { - for (node <- nodes) { - val metadata = nodeMeta(node) - val msg = node match { - case value: XdmNode => - new XdmNodeItemMessage(value, metadata, selectContext) - case value: XdmValue => - new XdmValueItemMessage(value, metadata, selectContext) - case value: BinaryNode => - val tree = new SaxonTreeBuilder(config) - tree.startDocument(metadata.baseURI) - tree.endDocument() - new AnyItemMessage(tree.result, value, metadata, selectContext) - case _ => - throw XProcException.xiThisCantHappen(s"Unexpected node type ${node}", location) - } - makeSelection(List(msg)) - } + val items = ListBuffer.empty[Tuple2[Any, XProcMetadata]] + for (node <- nodes) { + items += Tuple2(node, nodeMeta(node)) } - } - private def makeSelection(context: List[Message]): Unit = { - val expr = new XProcXPathExpression(selectContext, select, None, None, None) - val exprEval = config.expressionEvaluator.newInstance() - val result = exprEval.value(expr, context, msgBindings.toMap, None) - val iter = result.item.iterator() - var count = 0 - while (iter.hasNext) { - val item = iter.next() - count += 1 + val xpselector = new XPathSelector(config.config, items.toList, select, context, msgBindings.toMap) + val results = xpselector.select() - if (!ispec.cardinality("source").get.withinBounds(count)) { - throw XProcException.xdInputSequenceNotAllowed(port, location) - } + if (results.length != 1 && !sequence) { + throw XProcException.xdInputSequenceNotAllowed(port, None) + } - item match { + for (result <- results) { + result match { case node: XdmNode => - if (node.getNodeKind == XdmNodeKind.ATTRIBUTE) { - throw XProcException.xdInvalidSelection(select, "attribute", location) - } - val tree = new SaxonTreeBuilder(config) - tree.startDocument(node.getBaseURI) - tree.addSubtree(node) - tree.endDocument() - consume(tree.result, "result") + consume(node, "result") + case value: XdmValue => + consume(value, "result") case _ => - consume(item, "result") + throw XProcException.xiThisCantHappen("XPathSelector returned something that wasn't an XdmValue?") } } - - if (!ispec.cardinality("source").get.withinBounds(count)) { - throw XProcException.xdInputSequenceNotAllowed(port, location) - } } override def toString: String = { diff --git a/src/main/scala/com/xmlcalabash/steps/internal/ValueComputation.scala b/src/main/scala/com/xmlcalabash/steps/internal/ValueComputation.scala new file mode 100644 index 0000000..20f2dc8 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/steps/internal/ValueComputation.scala @@ -0,0 +1,55 @@ +package com.xmlcalabash.steps.internal + +import com.jafpl.graph.{ContainerStart, Node} +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.model.util.XProcConstants.ValueTemplate +import com.xmlcalabash.model.xxml.{XArtifact, XAtomicStep} +import com.xmlcalabash.runtime.params.XPathBindingParams +import com.xmlcalabash.runtime.{XMLCalabashRuntime, XProcVtExpression, XProcXPathExpression} +import net.sf.saxon.s9api.{QName, SequenceType, XdmAtomicValue} + +class ValueComputation private(parentStep: XArtifact, name: QName, collection: Boolean) extends XAtomicStep(parentStep.config, XProcConstants.cx_value_computation) { + private var _avt = Option.empty[ValueTemplate] + private var _select = Option.empty[String] + private var _as = Option.empty[SequenceType] + private var _tokens = Option.empty[List[XdmAtomicValue]] + staticContext = parentStep.staticContext + _synthetic = true + parent = parentStep + + def this(parentStep: XArtifact, name: QName, avt: ValueTemplate, collection: Boolean) = { + this(parentStep, name, collection) + _avt = Some(avt) + } + + def this(parentStep: XArtifact, name: QName, select: String, collection: Boolean) = { + this(parentStep, name, collection) + _select = Some(select) + } + + def valueName: QName = name + def avt: Option[ValueTemplate] = _avt + def select: Option[String] = _select + + def as: Option[SequenceType] = _as + protected[xmlcalabash] def as_=(seqType: SequenceType): Unit = { + _as = Some(seqType) + } + + def allowedValues: Option[List[XdmAtomicValue]] = _tokens + protected[xmlcalabash] def allowedValues_=(values: List[XdmAtomicValue]): Unit = { + _tokens = Some(values) + } + + override def graphNodes(runtime: XMLCalabashRuntime, parent: Node): Unit = { + val init = if (_avt.isDefined) { + new XProcVtExpression(staticContext, _avt.get, true) + } else { + val params = new XPathBindingParams(staticContext.inscopeConstantValues, collection) + new XProcXPathExpression(staticContext, _select.getOrElse("()"), as, _tokens, Some(params)) + } + + val cnode = runtime.node(ancestorContainer.get).asInstanceOf[ContainerStart] + runtime.addNode(this, cnode.addOption(name.getClarkName, init)) + } +} diff --git a/src/main/scala/com/xmlcalabash/steps/internal/XPathSelector.scala b/src/main/scala/com/xmlcalabash/steps/internal/XPathSelector.scala new file mode 100644 index 0000000..7bf067e --- /dev/null +++ b/src/main/scala/com/xmlcalabash/steps/internal/XPathSelector.scala @@ -0,0 +1,73 @@ +package com.xmlcalabash.steps.internal + +import com.jafpl.messages.Message +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.{AnyItemMessage, XdmNodeItemMessage, XdmValueItemMessage} +import com.xmlcalabash.model.util.SaxonTreeBuilder +import com.xmlcalabash.runtime.{BinaryNode, XProcMetadata, XProcXPathExpression} +import com.xmlcalabash.util.MinimalStaticContext +import net.sf.saxon.s9api.{XdmNode, XdmNodeKind, XdmValue} + +import scala.collection.mutable.ListBuffer + +class XPathSelector(config: XMLCalabash, + items: List[Tuple2[Any, XProcMetadata]], + select: String, + context: MinimalStaticContext, + bindings: Map[String,Message]) { + private val results = ListBuffer.empty[Any] + + def select(): List[Any] = { + if (items.isEmpty) { + makeSelection(List()) + } else { + for (item <- items) { + val node = item._1 + val metadata = item._2 + val msg = node match { + case value: XdmNode => + new XdmNodeItemMessage(value, metadata, context) + case value: XdmValue => + new XdmValueItemMessage(value, metadata, context) + case value: BinaryNode => + val tree = new SaxonTreeBuilder(config) + tree.startDocument(metadata.baseURI) + tree.endDocument() + new AnyItemMessage(tree.result, value, metadata, context) + case _ => + throw XProcException.xiThisCantHappen(s"Unexpected node type ${node}", context.location) + } + makeSelection(List(msg)) + } + } + + results.toList + } + + private def makeSelection(contextItem: List[Message]): Unit = { + val expr = new XProcXPathExpression(context, select, None, None, None) + val exprEval = config.expressionEvaluator.newInstance() + val result = exprEval.value(expr, contextItem, bindings, None) + val iter = result.item.iterator() + var count = 0 + while (iter.hasNext) { + val item = iter.next() + count += 1 + + item match { + case node: XdmNode => + if (node.getNodeKind == XdmNodeKind.ATTRIBUTE) { + throw XProcException.xdInvalidSelection(select, "attribute", context.location) + } + val tree = new SaxonTreeBuilder(config) + tree.startDocument(node.getBaseURI) + tree.addSubtree(node) + tree.endDocument() + results += tree.result + case _ => + results += item + } + } + } +} diff --git a/src/main/scala/com/xmlcalabash/steps/json/Join.scala b/src/main/scala/com/xmlcalabash/steps/json/Join.scala index 4efc8c4..1ff1c22 100644 --- a/src/main/scala/com/xmlcalabash/steps/json/Join.scala +++ b/src/main/scala/com/xmlcalabash/steps/json/Join.scala @@ -3,6 +3,7 @@ package com.xmlcalabash.steps.json import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{QName, XdmArray, XdmItem, XdmValue} import scala.collection.mutable.ListBuffer @@ -25,7 +26,7 @@ class Join extends DefaultXmlStep { } } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) val depth = bindings(_flatten_to_depth).value.getUnderlyingValue.getStringValue diff --git a/src/main/scala/com/xmlcalabash/steps/json/Merge.scala b/src/main/scala/com/xmlcalabash/steps/json/Merge.scala index 1b16768..83733b7 100644 --- a/src/main/scala/com/xmlcalabash/steps/json/Merge.scala +++ b/src/main/scala/com/xmlcalabash/steps/json/Merge.scala @@ -4,6 +4,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmItem, XdmMap, XdmNode, XdmValue} import scala.collection.mutable.ListBuffer @@ -30,7 +31,7 @@ class Merge extends DefaultXmlStep { } } - override def run(staticContext: StaticContext): Unit = { + override def run(staticContext: MinimalStaticContext): Unit = { super.run(staticContext) duplicates = bindings(_duplicates).value.getUnderlyingValue.getStringValue @@ -51,7 +52,7 @@ class Merge extends DefaultXmlStep { if (staticContext.baseURI.isDefined) { compiler.setBaseURI(staticContext.baseURI.get) } - for ((pfx, uri) <- staticContext.nsBindings) { + for ((pfx, uri) <- staticContext.inscopeNamespaces) { compiler.declareNamespace(pfx, uri) } compiler.declareVariable(p_index) diff --git a/src/main/scala/com/xmlcalabash/steps/os/OsExec.scala b/src/main/scala/com/xmlcalabash/steps/os/OsExec.scala index c1eb22f..702344e 100644 --- a/src/main/scala/com/xmlcalabash/steps/os/OsExec.scala +++ b/src/main/scala/com/xmlcalabash/steps/os/OsExec.scala @@ -6,7 +6,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{BinaryNode, StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, SaxonApiException, XdmNode} import java.io.{ByteArrayInputStream, File, IOException, InputStream, InputStreamReader} @@ -47,7 +47,7 @@ class OsExec extends DefaultXmlStep { sourceMeta = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) command = stringBinding(_command) diff --git a/src/main/scala/com/xmlcalabash/steps/os/OsInfo.scala b/src/main/scala/com/xmlcalabash/steps/os/OsInfo.scala index 122302c..6ec4025 100644 --- a/src/main/scala/com/xmlcalabash/steps/os/OsInfo.scala +++ b/src/main/scala/com/xmlcalabash/steps/os/OsInfo.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps.os import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.{MediaType, TypeUtils} +import com.xmlcalabash.util.{MediaType, MinimalStaticContext, TypeUtils} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} import net.sf.saxon.s9api.QName @@ -26,7 +26,7 @@ class OsInfo extends DefaultXmlStep { override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) var amap: AttributeMap = EmptyAttributeMap.getInstance() diff --git a/src/main/scala/com/xmlcalabash/steps/text/Count.scala b/src/main/scala/com/xmlcalabash/steps/text/Count.scala index 525a4fe..a052a6d 100644 --- a/src/main/scala/com/xmlcalabash/steps/text/Count.scala +++ b/src/main/scala/com/xmlcalabash/steps/text/Count.scala @@ -2,13 +2,13 @@ package com.xmlcalabash.steps.text import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} class Count() extends TextLines { override def inputSpec: XmlPortSpecification = XmlPortSpecification.TEXTSOURCE override def outputSpec: XmlPortSpecification = XmlPortSpecification.XMLRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val builder = new SaxonTreeBuilder(config) diff --git a/src/main/scala/com/xmlcalabash/steps/text/Head.scala b/src/main/scala/com/xmlcalabash/steps/text/Head.scala index a583aa2..c82c9d1 100644 --- a/src/main/scala/com/xmlcalabash/steps/text/Head.scala +++ b/src/main/scala/com/xmlcalabash/steps/text/Head.scala @@ -1,7 +1,7 @@ package com.xmlcalabash.steps.text import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmAtomicValue} import scala.collection.mutable.ListBuffer @@ -12,7 +12,7 @@ class Head() extends TextLines { override def inputSpec: XmlPortSpecification = XmlPortSpecification.TEXTSOURCE override def outputSpec: XmlPortSpecification = XmlPortSpecification.TEXTRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val count = integerBinding(_count).get diff --git a/src/main/scala/com/xmlcalabash/steps/text/Join.scala b/src/main/scala/com/xmlcalabash/steps/text/Join.scala index b49bd1a..666b61c 100644 --- a/src/main/scala/com/xmlcalabash/steps/text/Join.scala +++ b/src/main/scala/com/xmlcalabash/steps/text/Join.scala @@ -4,7 +4,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmNode} import scala.collection.mutable.ListBuffer @@ -26,7 +26,7 @@ class Join() extends DefaultXmlStep { } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val separator = stringBinding(_separator) diff --git a/src/main/scala/com/xmlcalabash/steps/text/Replace.scala b/src/main/scala/com/xmlcalabash/steps/text/Replace.scala index e810ee4..9a31982 100644 --- a/src/main/scala/com/xmlcalabash/steps/text/Replace.scala +++ b/src/main/scala/com/xmlcalabash/steps/text/Replace.scala @@ -5,6 +5,7 @@ import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.XdmNodeItemMessage import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XProcXPathExpression, XmlPortSpecification} import com.xmlcalabash.steps.DefaultXmlStep +import com.xmlcalabash.util.MinimalStaticContext import net.sf.saxon.s9api.{QName, XdmNode} class Replace() extends DefaultXmlStep { @@ -26,7 +27,7 @@ class Replace() extends DefaultXmlStep { meta = metadata } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val pattern = stringBinding(_pattern).replace("'", "''") diff --git a/src/main/scala/com/xmlcalabash/steps/text/Sort.scala b/src/main/scala/com/xmlcalabash/steps/text/Sort.scala index f9536b8..3361408 100644 --- a/src/main/scala/com/xmlcalabash/steps/text/Sort.scala +++ b/src/main/scala/com/xmlcalabash/steps/text/Sort.scala @@ -3,7 +3,7 @@ package com.xmlcalabash.steps.text import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants import com.xmlcalabash.runtime.{NameValueBinding, StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import com.xmlcalabash.util.xc.XsltStylesheet import javax.xml.transform.{ErrorListener, TransformerException} @@ -31,14 +31,14 @@ class Sort() extends TextLines { override def receiveBinding(variable: NameValueBinding): Unit = { super.receiveBinding(variable) if (variable.name == _sort_key) { - keyNamespaceBindings = variable.context.nsBindings + keyNamespaceBindings = variable.context.inscopeNamespaces } } - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) - val xslbuilder = new XsltStylesheet(config, context.nsBindings, List(), "2.0") + val xslbuilder = new XsltStylesheet(config, context.inscopeNamespaces, List(), "2.0") xslbuilder.startVariable("lines", "element()*") for (line <- lines) { diff --git a/src/main/scala/com/xmlcalabash/steps/text/Tail.scala b/src/main/scala/com/xmlcalabash/steps/text/Tail.scala index 06198e4..bc7aa8f 100644 --- a/src/main/scala/com/xmlcalabash/steps/text/Tail.scala +++ b/src/main/scala/com/xmlcalabash/steps/text/Tail.scala @@ -1,7 +1,7 @@ package com.xmlcalabash.steps.text import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmAtomicValue} import scala.collection.mutable.ListBuffer @@ -12,7 +12,7 @@ class Tail() extends TextLines { override def inputSpec: XmlPortSpecification = XmlPortSpecification.TEXTSOURCE override def outputSpec: XmlPortSpecification = XmlPortSpecification.TEXTRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { super.run(context) val count = integerBinding(_count).get diff --git a/src/main/scala/com/xmlcalabash/testing/TestRunner.scala b/src/main/scala/com/xmlcalabash/testing/TestRunner.scala index 2246189..8eb7fa5 100644 --- a/src/main/scala/com/xmlcalabash/testing/TestRunner.scala +++ b/src/main/scala/com/xmlcalabash/testing/TestRunner.scala @@ -5,11 +5,11 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.TestException import com.xmlcalabash.messages.XdmNodeItemMessage import com.xmlcalabash.model.util.{SaxonTreeBuilder, ValueParser, XProcConstants} -import com.xmlcalabash.model.xml.XMLContext +import com.xmlcalabash.model.xxml.{XMLStaticContext, XStaticContext} import com.xmlcalabash.runtime.{SaxonExpressionEvaluator, StaticContext, XProcLocation, XProcMetadata, XProcXPathExpression} import com.xmlcalabash.util.{MediaType, S9Api, TypeUtils, URIUtils, Urify} import net.sf.saxon.om.{AttributeMap, EmptyAttributeMap} -import net.sf.saxon.s9api.{Axis, Processor, QName, XdmNode, XdmNodeKind, XdmValue} +import net.sf.saxon.s9api.{Axis, ItemType, Processor, QName, XdmAtomicValue, XdmNode, XdmNodeKind, XdmValue} import org.slf4j.{Logger, LoggerFactory} import org.xml.sax.InputSource @@ -59,6 +59,7 @@ class TestRunner(processor: Processor, online: Boolean, regex: Option[String], t private val t_schematron = new QName(tsns, "schematron") private val t_input = new QName(tsns, "input") private val t_option = new QName(tsns, "option") + private val _as = new QName("", "as") private val _src = new QName("", "src") private val _port = new QName("", "port") private val _name = new QName("", "name") @@ -560,12 +561,6 @@ class TestRunner(processor: Processor, online: Boolean, regex: Option[String], t var urifyFeature = Option.empty[String] val features = node.getAttributeValue(_features) if (features != null) { - if (features.contains("lazy-eval")) { - val result = new TestResult(true) // skipped counts as a pass... - result.baseURI = node.getBaseURI - result.skipped = "The 'lazy-eval' feature is not supported" - return result - } if (features.contains("xslt-1")) { xmlcalabash.args.config(XProcConstants.cc_xslt10_classpath, Urify.urify("src/test/resources/saxon-6.5.5.jar")) } @@ -775,10 +770,13 @@ class TestRunner(processor: Processor, online: Boolean, regex: Option[String], t } } + /* if (!result.passed && result.exception.isDefined) { result.exception.get.printStackTrace() } + */ + result } } @@ -814,13 +812,19 @@ class TestRunner(processor: Processor, online: Boolean, regex: Option[String], t val src = node.getAttributeValue(_src) if ((src == null) && children.isEmpty) { - val scontext = new XMLContext(emptyConfig, Some(node.getBaseURI), S9Api.inScopeNamespaces(node), Some(new XProcLocation(node))) - val value = node.getAttributeValue(_select) + val scontext = new XStaticContext(new XProcLocation(node), S9Api.inScopeNamespaces(node)) + val select = node.getAttributeValue(_select) val contextItem = inlineDocument(node) val message = new XdmNodeItemMessage(contextItem.get, new XProcMetadata(MediaType.XML), scontext) val eval = emptyConfig.expressionEvaluator.newInstance() - val result = eval.singletonValue(new XProcXPathExpression(scontext, value), List(message), Map.empty[String,Message], None) - Some(result.item) + val result = eval.singletonValue(new XProcXPathExpression(scontext, select), List(message), Map.empty[String,Message], None) + + var value = result.item + if (node.getAttributeValue(_as) == null) { + value = TypeUtils.castAtomicAs(value.asInstanceOf[XdmAtomicValue], ItemType.UNTYPED_ATOMIC, scontext) + } + + Some(value) } else { loadResource(node) } diff --git a/src/main/scala/com/xmlcalabash/util/ArgBundle.scala b/src/main/scala/com/xmlcalabash/util/ArgBundle.scala index 82118fa..f7e648d 100644 --- a/src/main/scala/com/xmlcalabash/util/ArgBundle.scala +++ b/src/main/scala/com/xmlcalabash/util/ArgBundle.scala @@ -1,18 +1,13 @@ package com.xmlcalabash.util -import com.jafpl.messages.Message import com.jafpl.steps.DataConsumer -import com.xmlcalabash.config.{DocumentRequest, XMLCalabashDebugOptions} +import com.xmlcalabash.config.XMLCalabashDebugOptions import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.messages.XdmValueItemMessage -import com.xmlcalabash.model.util.{ValueParser, XProcConstants} -import com.xmlcalabash.model.xml.XMLContext -import com.xmlcalabash.runtime.{XProcMetadata, XProcXPathExpression} -import net.sf.saxon.lib.NamespaceConstant -import net.sf.saxon.s9api.{Axis, ItemTypeFactory, Processor, QName, XdmAtomicValue, XdmNode, XdmNodeKind, XdmValue} +import com.xmlcalabash.model.util.XProcConstants +import net.sf.saxon.s9api.{Axis, Processor, QName, XdmNode, XdmNodeKind, XdmValue} import org.slf4j.{Logger, LoggerFactory} -import java.io.File +import java.io.{BufferedReader, File, InputStreamReader} import java.net.URI import java.util.Properties import scala.collection.mutable @@ -544,61 +539,64 @@ class ArgBundle() { } } - def loadProperties(): Unit = { - val uriEnum = this.getClass.getClassLoader.getResources("com.xmlcalabash.properties") + def loadSettings(): Unit = { + val NSPattern = "namespace\\s+(.+)$".r + val FPattern = "function\\s+(.+):(.+)$".r + val SPattern = "step\\s+(.+):(.+)$".r + + // These started out as .properties files, but it turned out that didn't (conveniently) work + val uriEnum = this.getClass.getClassLoader.getResources("com.xmlcalabash.settings") while (uriEnum.hasMoreElements) { val url = uriEnum.nextElement() - logger.debug(s"Loading properties: $url") + logger.debug(s"Loading settings: $url") val conn = url.openConnection() val stream = conn.getInputStream - val props = new Properties() - props.load(stream) val nsmap = mutable.HashMap.empty[String,String] - val NSPattern = "namespace\\s+(.+)$".r - val FPattern = "function\\s+(.+):(.+)$".r - val SPattern = "step\\s+(.+):(.+)$".r - - // Properties are unordered so find the namespace bindings - var propIter = props.stringPropertyNames().iterator() - while (propIter.hasNext) { - val name = propIter.next() - val value = props.get(name).asInstanceOf[String] - value match { - case NSPattern(uri) => - if (nsmap.contains(name)) { - throw new RuntimeException("Cannot redefine namespace bindings in property file") - } - nsmap.put(name, uri) - case _ => () - } - } - // Now parse the step and function declarations - propIter = props.stringPropertyNames().iterator() - while (propIter.hasNext) { - val name = propIter.next() - val value = props.get(name).asInstanceOf[String] - value match { - case NSPattern(uri) => () - case FPattern(pfx,local) => - if (nsmap.contains(pfx)) { - val qname = new QName(pfx, nsmap(pfx), local) - _parameters += new PipelineFunctionImplementation(qname.getEQName, value, name) - } else { - logger.debug(s"No namespace binding for $pfx, ignoring: $name=$value") - } - case SPattern(pfx,local) => - if (nsmap.contains(pfx)) { - val qname = new QName(pfx, nsmap(pfx), local) - _parameters += new PipelineStepImplementation(qname.getEQName, value, name) - } else { - logger.debug(s"No namespace binding for $pfx, ignoring: $name=$value") + val reader = new BufferedReader(new InputStreamReader(stream)) + var line = reader.readLine() + while (line != null) { + var pos = line.indexOf("#") + if (pos >= 0) { + line = line.substring(0, pos) + } + line = line.trim() + if (line != "") { + pos = line.indexOf("=") + if (pos <= 0) { + logger.warn(s"Ignoring unparsable setting: ${line}") + } else { + val name = line.substring(0, pos).trim + val value = line.substring(pos+1).trim + name match { + case NSPattern(uri) => + if (nsmap.contains(name)) { + throw new RuntimeException("Cannot redefine namespace bindings in property file") + } + nsmap.put(value, uri) + case FPattern(pfx,local) => + if (nsmap.contains(pfx)) { + val qname = new QName(pfx, nsmap(pfx), local) + _parameters += new PipelineFunctionImplementation(qname.getEQName, name, value) + } else { + logger.debug(s"No namespace binding for $pfx, ignoring: $name=$value") + } + case SPattern(pfx,local) => + if (nsmap.contains(pfx)) { + val qname = new QName(pfx, nsmap(pfx), local) + _parameters += new PipelineStepImplementation(qname.getEQName, name, value) + } else { + logger.debug(s"No namespace binding for $pfx, ignoring: $name=$value") + } + case _ => + logger.debug(s"Unparseable property, ignoring: $name=$value") } - case _ => - logger.debug(s"Unparseable property, ignoring: $name=$value") + } } + + line = reader.readLine() } } } diff --git a/src/main/scala/com/xmlcalabash/util/DefaultDocumentManager.scala b/src/main/scala/com/xmlcalabash/util/DefaultDocumentManager.scala index 07959af..9014127 100644 --- a/src/main/scala/com/xmlcalabash/util/DefaultDocumentManager.scala +++ b/src/main/scala/com/xmlcalabash/util/DefaultDocumentManager.scala @@ -10,8 +10,7 @@ import com.xmlcalabash.messages.XdmValueItemMessage import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{BinaryNode, StaticContext, XProcMetadata, XProcXPathExpression} import com.xmlcalabash.util.xc.Errors -import net.sf.saxon.Configuration -import net.sf.saxon.lib.{AugmentedSource, ErrorReporter, ParseOptions} +import net.sf.saxon.lib.{AugmentedSource, ParseOptions} import net.sf.saxon.s9api.{QName, SaxonApiException, XdmAtomicValue, XdmNode, XdmValue} import net.sf.saxon.trans.XPathException import nu.validator.htmlparser.common.XmlViolationPolicy @@ -21,7 +20,6 @@ import org.apache.http.client.utils.DateUtils import org.apache.http.impl.client.HttpClients import org.apache.http.util.ByteArrayBuffer import org.slf4j.{Logger, LoggerFactory} -import org.xml.sax.helpers.XMLReaderFactory import org.xml.sax.{InputSource, SAXException} import java.io.{File, FileInputStream, FileNotFoundException, IOException, InputStream, UnsupportedEncodingException} diff --git a/src/main/scala/com/xmlcalabash/util/DefaultErrorExplanation.scala b/src/main/scala/com/xmlcalabash/util/DefaultErrorExplanation.scala index e0eede5..561e746 100644 --- a/src/main/scala/com/xmlcalabash/util/DefaultErrorExplanation.scala +++ b/src/main/scala/com/xmlcalabash/util/DefaultErrorExplanation.scala @@ -138,7 +138,7 @@ class DefaultErrorExplanation() extends ErrorExplanation { } override def explanation(code: QName, variant: Int, details: List[Any]): String = { - var message = template(code, variant, details.length).explanation + val message = template(code, variant, details.length).explanation substitute(message, details) } diff --git a/src/main/scala/com/xmlcalabash/util/DefaultXMLCalabashConfigurer.scala b/src/main/scala/com/xmlcalabash/util/DefaultXMLCalabashConfigurer.scala index 378b9d3..7839b5f 100644 --- a/src/main/scala/com/xmlcalabash/util/DefaultXMLCalabashConfigurer.scala +++ b/src/main/scala/com/xmlcalabash/util/DefaultXMLCalabashConfigurer.scala @@ -19,7 +19,7 @@ class DefaultXMLCalabashConfigurer() extends XMLCalabashConfigurer { override def configure(input: List[PipelineParameter]): List[PipelineParameter] = { val args = new ArgBundle() - args.loadProperties() + args.loadSettings() val searchProp = ListBuffer.empty[PipelineParameter] ++ input val propConfig = Option(System.getProperty("com.xmlcalabash.configFile")) diff --git a/src/main/scala/com/xmlcalabash/util/InternetProtocolRequest.scala b/src/main/scala/com/xmlcalabash/util/InternetProtocolRequest.scala index 1d4a5c6..938852b 100644 --- a/src/main/scala/com/xmlcalabash/util/InternetProtocolRequest.scala +++ b/src/main/scala/com/xmlcalabash/util/InternetProtocolRequest.scala @@ -4,6 +4,7 @@ import com.jafpl.graph.Location import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.model.xxml.XStaticContext import com.xmlcalabash.runtime.{StaticContext, XMLCalabashRuntime, XProcMetadata, XProcXPathExpression} import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmMap, XdmValue} import org.apache.http.auth.{AuthScope, UsernamePasswordCredentials} @@ -27,7 +28,7 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava} -class InternetProtocolRequest(val config: XMLCalabash, val context: StaticContext, val uri: URI) { +class InternetProtocolRequest(val config: XMLCalabash, val context: MinimalStaticContext, val uri: URI) { private val _expires = new QName("", "expires") private val _date = new QName("", "date") private val _content_disposition = new QName("", "content-disposition") @@ -53,11 +54,11 @@ class InternetProtocolRequest(val config: XMLCalabash, val context: StaticContex private var _followRedirectCount = -1 private var _sendBodyAnyway = false - def this(config: XMLCalabashRuntime, context: StaticContext, uri: URI) = + def this(config: XMLCalabashRuntime, context: MinimalStaticContext, uri: URI) = this(config.config, context, uri) def this(config: XMLCalabash, uri: URI) = - this(config, new StaticContext(config), uri) + this(config, new XStaticContext(), uri) def location: Option[Location] = _location def location_=(loc: Location): Unit = { diff --git a/src/main/scala/com/xmlcalabash/util/MediaType.scala b/src/main/scala/com/xmlcalabash/util/MediaType.scala index 07d2043..132fb34 100644 --- a/src/main/scala/com/xmlcalabash/util/MediaType.scala +++ b/src/main/scala/com/xmlcalabash/util/MediaType.scala @@ -10,6 +10,7 @@ import scala.collection.mutable.ListBuffer // N.B. This class accepts "*" for type and subtype because it's used for matching object MediaType { + val ANY = new MediaType("*", "*") val OCTET_STREAM = new MediaType("application", "octet-stream") val TEXT = new MediaType("text", "plain") val XML = new MediaType("application", "xml") @@ -21,18 +22,18 @@ object MediaType { val MULTIPART = new MediaType("multipart", "*") val MULTIPART_MIXED = new MediaType("multipart", "mixed") - val MATCH_XML: Array[MediaType] = Array( + val MATCH_XML: List[MediaType] = List( MediaType.parse("application/xml"), MediaType.parse("text/xml"), MediaType.parse("*/*+xml"), MediaType.parse("-application/xhtml+xml")) - val MATCH_HTML: Array[MediaType] = Array( + val MATCH_HTML: List[MediaType] = List( MediaType.parse("text/html"), MediaType.parse("application/xhtml+xml") ) - val MATCH_TEXT: Array[MediaType] = Array( + val MATCH_TEXT: List[MediaType] = List( MediaType.parse("text/*"), MediaType.parse("application/relax-ng-compact-syntax"), MediaType.parse("application/xquery"), @@ -41,16 +42,16 @@ object MediaType { MediaType.parse("-text/xml") ) - val MATCH_JSON: Array[MediaType] = Array( + val MATCH_JSON: List[MediaType] = List( MediaType.parse("application/json"), MediaType.parse("*/*+json") ) - val MATCH_YAML: Array[MediaType] = Array( + val MATCH_YAML: List[MediaType] = List( MediaType.parse("application/vnd.yaml") ) - val MATCH_ANY: Array[MediaType] = Array( + val MATCH_ANY: List[MediaType] = List( MediaType.parse("*/*") ) @@ -142,21 +143,41 @@ object MediaType { } def parseList(ctypes: String): ListBuffer[MediaType] = { + val fullyQualifiedExt = "^((-?)([^/\\s]+)/([^\\s;+]+)(\\+[^/\\s;]+)?(;\\s*([^\\s]+))?)\\s*(.*)$".r + val xmlShortcut = "^xml\\s+(.*)$".r + val htmlShortcut = "^html\\s+(.*)$".r + val textShortcut = "^text\\s+(.*)$".r + val jsonShortcut = "^json\\s+(.*)$".r + val anyShortcut = "^any\\s+(.*)$".r + val contentTypes = ListBuffer.empty[MediaType] - for (ctype <- ctypes.split("\\s+")) { - ctype match { - case "xml" => contentTypes ++= MATCH_XML - case "html" => contentTypes ++= MATCH_HTML - case "text" => contentTypes ++= MATCH_TEXT - case "json" => contentTypes ++= MATCH_JSON - case "any" => contentTypes ++= MATCH_ANY - case _ => - if (ctype.indexOf("/") <= 0) { - throw XProcException.xsUnrecognizedContentTypeShortcut(ctype, None) - } + + var typelist = ctypes + " " + while (typelist.trim != "") { + typelist match { + case fullyQualifiedExt(ctype, minus, mediatype, mediasubtype, ext, semi, params, rest) => contentTypes += MediaType.parse(ctype) + typelist = rest + case xmlShortcut(rest) => + contentTypes ++= MATCH_XML + typelist = rest + case htmlShortcut(rest) => + contentTypes ++= MATCH_HTML + typelist = rest + case textShortcut(rest) => + contentTypes ++= MATCH_TEXT + typelist = rest + case jsonShortcut(rest) => + contentTypes ++= MATCH_JSON + typelist = rest + case anyShortcut(rest) => + contentTypes ++= MATCH_ANY + typelist = rest + case _ => + throw XProcException.xsUnrecognizedContentTypeShortcut(typelist, None) } } + contentTypes } } @@ -283,7 +304,7 @@ class MediaType(val mediaType: String, val mediaSubtype: String, val suffix: Opt xmlContentType || htmlContentType } - def matchingMediaType(mtypes: Array[MediaType]): Option[MediaType] = { + def matchingMediaType(mtypes: List[MediaType]): Option[MediaType] = { var matching = Option.empty[MediaType] for (mtype <- mtypes) { //println(s"$this $mtype ${matches(mtype)}") diff --git a/src/main/scala/com/xmlcalabash/util/MinimalStaticContext.scala b/src/main/scala/com/xmlcalabash/util/MinimalStaticContext.scala new file mode 100644 index 0000000..d25497a --- /dev/null +++ b/src/main/scala/com/xmlcalabash/util/MinimalStaticContext.scala @@ -0,0 +1,275 @@ +package com.xmlcalabash.util + +import com.jafpl.graph.Location +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.model.xxml.{XArtifact, XNameBinding, XOption} +import net.sf.saxon.s9api.{ItemType, ItemTypeFactory, OccurrenceIndicator, QName, SaxonApiException, SequenceType, XdmAtomicValue, XdmNode} + +import java.net.URI + +abstract class MinimalStaticContext() { + def baseURI: Option[URI] + + def location: Option[Location] + + def inscopeNamespaces: Map[String, String] + + def inscopeConstants: Map[QName, XNameBinding] + + def parseClarkName(name: String): QName = { + parseClarkName(name, None) + } + + def parseClarkName(name: String, prefix: String): QName = { + parseClarkName(name, Some(prefix)) + } + + private def parseClarkName(name: String, pfx: Option[String]): QName = { + // FIXME: Better error handling for ClarkName parsing + if (name.startsWith("{")) { + val pos = name.indexOf("}") + val uri = name.substring(1, pos) + val local = name.substring(pos + 1) + if (pfx.isDefined) { + new QName(pfx.get, uri, local) + } else { + new QName(uri, local) + } + } else { + new QName("", name) + } + } + + def parseQName(name: String): QName = { + parseQName(Some(name)).get + } + + def parseQName(optname: Option[String]): Option[QName] = { + if (optname.isDefined) { + val name = optname.get + + if (name.startsWith("Q{")) { + val pos = name.lastIndexOf("}") + if (pos < 0) { + throw XProcException.xdCannotResolveQName(name, None) + } + val uri = name.substring(2, pos) + val local = parseNCName(name.substring(pos + 1)) + Some(new QName(uri, local)) + } else if (name.contains(":")) { + val pos = name.indexOf(":") + val pfx = parseNCName(name.substring(0, pos)) + val local = parseNCName(name.substring(pos + 1)) + val uri = inscopeNamespaces.get(pfx) + if (uri.isEmpty) { + throw XProcException.xdCannotResolveQName(name, None) + } + Some(new QName(pfx, uri.get, local)) + } else { + Some(new QName("", parseNCName(name))) + } + } else { + None + } + } + + def parseNCName(name: String): String = { + parseNCName(Some(name)).get + } + + def parseNCName(name: Option[String]): Option[String] = { + if (name.isDefined) { + try { + val ncname = castAtomicAs(new XdmAtomicValue(name.get), ItemType.NCNAME) + Some(ncname.getStringValue) + } catch { + case _: SaxonApiException => + throw XProcException.xsBadTypeValue(name.get, "NCName", None) + case e: Exception => + throw e + } + } else { + None + } + } + + def parseSequenceType(seqType: Option[String], typeFactory: ItemTypeFactory): Option[SequenceType] = { + if (seqType.isDefined) { + Some(parseSequenceType(seqType.get, typeFactory)) + } else { + None + } + } + + def parseSequenceType(seqType: String, typeFactory: ItemTypeFactory): SequenceType = { + // XPathParser.parseSequenceType returns a type.SequenceType. + // I need an s9api.SequenceType. Michael Kay confirms there's no + // easy way to convert between them. I'm rolling my own until such time + // as there's a better way. If it has to stay this way, I should call + // the actual XPath 3.1 parser for it, but I'm just going to hack my + // way through it for now. + val parensre = "^\\((.*)\\)$".r + val stypere = "^([^*+?()]+)\\s*([*+?])?$".r + val mtypere = "^map\\s*\\((.*)\\)\\s*([*+?])?$".r + val atypere = "^array\\s*\\((.*)\\)\\s*([*+?])?$".r + val ftypere = "^function\\s*\\((.*)\\)\\s*([*+?])?$".r + val itemre = "^item\\s*\\(\\s*\\)\\s*([*+?])?$".r + seqType.trim() match { + case parensre(body) => + parseSequenceType(body, typeFactory) + case stypere(typename, cardchar) => + // xs:sometype? + try { + val itype = TypeUtils.simpleType(parseQName(typename)) + SequenceType.makeSequenceType(itype, cardinality(cardchar)) + } catch { + case ex: XProcException => + if (ex.code == XProcException.err_xd0036) { + throw XProcException.xsInvalidSequenceType(typename, "Expected QName", None) + } + throw ex + case ex: Throwable => + throw ex + } + case mtypere(mbody, cardchar) => + if (mbody.trim == "*") { + return SequenceType.makeSequenceType(ItemType.ANY_MAP, cardinality(cardchar)) + } + val tuplere = "^([^,]+),(.*)$".r + mbody match { + case tuplere(keyseqtype, itemseqtype) => + val keytype = TypeUtils.simpleType(parseQName(keyseqtype)) + val itemtype = parseSequenceType(itemseqtype, typeFactory) + SequenceType.makeSequenceType(typeFactory.getMapType(keytype, itemtype), cardinality(cardchar)) + case _ => + throw new RuntimeException(s"Unexpected map syntax: map($mbody)") + } + case atypere(abody, cardchar) => + if (abody.trim == "*") { + return SequenceType.makeSequenceType(ItemType.ANY_ARRAY, cardinality(cardchar)) + } + val itemtype = parseSequenceType(abody, typeFactory) + SequenceType.makeSequenceType(typeFactory.getArrayType(itemtype), cardinality(cardchar)) + case ftypere(params, cardchar) => + SequenceType.makeSequenceType(ItemType.ANY_FUNCTION, cardinality(cardchar)) + case itemre(cardchar) => + SequenceType.makeSequenceType(ItemType.ANY_ITEM, cardinality(cardchar)) + case _ => + throw new RuntimeException(s"Unexpected sequence type: $seqType") + } + } + + def parseFakeMapSequenceType(seqType: String, typeFactory: ItemTypeFactory): SequenceType = { + // This is just like parseSequenceType except that it lies about the type of maps + val mtypere = "^map\\s*\\((.*)\\)\\s*([*+?])?$".r + seqType.trim() match { + case mtypere(mbody, cardchar) => + // I actually only care about the case where the keys are QNames + if (mbody.trim == "*") { + return SequenceType.makeSequenceType(ItemType.ANY_MAP, cardinality(cardchar)) + } + val tuplere = "^([^,]+),(.*)$".r + mbody match { + case tuplere(keyseqtype, itemseqtype) => + val keytype = ItemType.ANY_ATOMIC_VALUE + val itemtype = parseSequenceType(itemseqtype, typeFactory) + SequenceType.makeSequenceType(typeFactory.getMapType(keytype, itemtype), cardinality(cardchar)) + case _ => + throw new RuntimeException(s"Unexpected map syntax: map($mbody)") + } + case _ => + parseSequenceType(seqType, typeFactory) + } + } + + private def cardinality(card: String): OccurrenceIndicator = { + if (card == null) { + OccurrenceIndicator.ONE + } else { + card match { + case "*" => OccurrenceIndicator.ZERO_OR_MORE + case "?" => OccurrenceIndicator.ZERO_OR_ONE + case "+" => OccurrenceIndicator.ONE_OR_MORE + case _ => + throw new RuntimeException(s"Unexpected cardinality $card") + } + } + } + + def parseBoolean(value: Option[String]): Option[Boolean] = { + if (value.isDefined) { + if (value.get == "true" || value.get == "false") { + Some(value.get == "true") + } else { + throw XProcException.xsBadTypeValue(value.get, "boolean", None) + } + } else { + None + } + } + + def parseSingleContentType(ctype: Option[String]): Option[MediaType] = { + if (ctype.isDefined) { + MediaType.parse(ctype) + } else { + None + } + } + + def parseContentTypes(ctypes: Option[String]): List[MediaType] = { + if (ctypes.isDefined) { + try { + MediaType.parseList(ctypes.get).toList + } catch { + case ex: XProcException => + if (ex.code == XProcException.err_xc0070) { + // Map to the static error... + throw XProcException.xsUnrecognizedContentTypeShortcut(ex.details.head.toString, ex.location) + } else { + throw ex + } + } + } else { + List.empty[MediaType] + } + } + + def castAtomicAs(value: XdmAtomicValue, seqType: Option[SequenceType]): XdmAtomicValue = { + if (seqType.isEmpty) { + return value + } + + castAtomicAs(value, seqType.get.getItemType) + } + + def castAtomicAs(value: XdmAtomicValue, xsdtype: ItemType): XdmAtomicValue = { + if ((xsdtype == ItemType.UNTYPED_ATOMIC) || (xsdtype == ItemType.STRING || (xsdtype == ItemType.ANY_ITEM))) { + return value + } + + if (xsdtype == ItemType.QNAME) { + val qnamev = value.getPrimitiveTypeName match { + case XProcConstants.xs_string => new XdmAtomicValue(parseQName(value.getStringValue)) + case XProcConstants.xs_untypedAtomic => new XdmAtomicValue(parseQName(value.getStringValue)) + case XProcConstants.xs_QName => value + case _ => + throw new RuntimeException(s"Don't know how to convert $value to an xs:QName") + } + return qnamev + } + + try { + new XdmAtomicValue(value.getStringValue, xsdtype) + } catch { + case sae: SaxonApiException => + if (sae.getMessage.contains("Invalid URI")) { + throw XProcException.xdInvalidURI(value.getStringValue, location) + } else { + throw XProcException.xdBadType(value.getStringValue, xsdtype.toString, location) + } + case ex: Exception => + throw(ex) + } + } +} diff --git a/src/main/scala/com/xmlcalabash/util/PipelineParameter.scala b/src/main/scala/com/xmlcalabash/util/PipelineParameter.scala index cf75649..d98a1aa 100644 --- a/src/main/scala/com/xmlcalabash/util/PipelineParameter.scala +++ b/src/main/scala/com/xmlcalabash/util/PipelineParameter.scala @@ -1,6 +1,7 @@ package com.xmlcalabash.util import com.jafpl.steps.DataConsumer +import com.xmlcalabash.model.xxml.XStaticContext import com.xmlcalabash.runtime.StaticContext import net.sf.saxon.s9api.{QName, XdmNode, XdmValue} import org.slf4j.LoggerFactory @@ -118,7 +119,7 @@ class PipelineExpressionOption(eqname: String, val expression: String) extends P override def toString = s"${eqname} = XPath: ${expression}" } -class PipelineOptionValue(val context: StaticContext, val value: XdmValue) extends PipelineParameter {} +class PipelineOptionValue(val context: XStaticContext, val value: XdmValue) extends PipelineParameter {} class PipelineSystemProperty(val name: String, val value: String) extends PipelineParameter { override def toString = s"${name} = ${value}" diff --git a/src/main/scala/com/xmlcalabash/util/S9Api.scala b/src/main/scala/com/xmlcalabash/util/S9Api.scala index b7c0207..2b092dc 100644 --- a/src/main/scala/com/xmlcalabash/util/S9Api.scala +++ b/src/main/scala/com/xmlcalabash/util/S9Api.scala @@ -198,7 +198,7 @@ object S9Api { tree.result } - def forceQNameKeys(inputMap: MapItem, context: StaticContext): XdmMap = { + def forceQNameKeys(inputMap: MapItem, context: MinimalStaticContext): XdmMap = { var map = new XdmMap() val iter = inputMap.keyValuePairs().iterator() @@ -206,7 +206,7 @@ object S9Api { val pair = iter.next() pair.key.getItemType match { case BuiltInAtomicType.STRING => - val qname = ValueParser.parseQName(pair.key.getStringValue, context) + val qname = context.parseQName(pair.key.getStringValue) map = map.put(new XdmAtomicValue(qname), XdmValue.wrap(pair.value)) case BuiltInAtomicType.QNAME => val qvalue = pair.key.asInstanceOf[QNameValue] @@ -315,7 +315,11 @@ object S9Api { } if (!found) { - throw new RuntimeException("No binding for prefix: " + pfx) + if (pfx == "#default") { + throw XProcException.xsBadExcludeInlinePrefixesDefault() + } else { + throw XProcException.xsBadExcludeInlinePrefixes(pfx) + } } } diff --git a/src/main/scala/com/xmlcalabash/util/TvtExpander.scala b/src/main/scala/com/xmlcalabash/util/TvtExpander.scala index fb0f161..5ea40fb 100644 --- a/src/main/scala/com/xmlcalabash/util/TvtExpander.scala +++ b/src/main/scala/com/xmlcalabash/util/TvtExpander.scala @@ -5,6 +5,7 @@ import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.XProcItemMessage import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} +import com.xmlcalabash.model.xxml.XStaticContext import com.xmlcalabash.runtime.{StaticContext, XProcVtExpression} import net.sf.saxon.`type`.BuiltInAtomicType import net.sf.saxon.event.ReceiverOption @@ -16,7 +17,7 @@ import scala.jdk.CollectionConverters.ListHasAsScala class TvtExpander(config: XMLCalabash, contextItem: Option[XProcItemMessage], - exprContext: StaticContext, + exprContext: XStaticContext, msgBindings: Map[String, XProcItemMessage], location: Option[Location]) { private val excludeURIs = mutable.HashSet.empty[String] diff --git a/src/main/scala/com/xmlcalabash/util/TypeUtils.scala b/src/main/scala/com/xmlcalabash/util/TypeUtils.scala index 8878a53..758ae18 100644 --- a/src/main/scala/com/xmlcalabash/util/TypeUtils.scala +++ b/src/main/scala/com/xmlcalabash/util/TypeUtils.scala @@ -1,18 +1,21 @@ package com.xmlcalabash.util import com.xmlcalabash.XMLCalabash - -import java.util import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.util.{ValueParser, XProcConstants} -import com.xmlcalabash.parsers.SequenceBuilder -import com.xmlcalabash.runtime.{StaticContext, XMLCalabashRuntime} +import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.runtime.XMLCalabashRuntime import jdk.nashorn.api.scripting.ScriptObjectMirror -import net.sf.saxon.`type`.BuiltInAtomicType +import net.sf.saxon.`type`.{BuiltInAtomicType, TypeHierarchy, ValidationException} import net.sf.saxon.event.ReceiverOption +import net.sf.saxon.expr.parser.{Loc, RoleDiagnostic} import net.sf.saxon.om.{AttributeInfo, FingerprintedQName} import net.sf.saxon.s9api._ +import net.sf.saxon.trans.XPathException +import net.sf.saxon.value.StringValue +import org.slf4j.{Logger, LoggerFactory} +import java.net.URI import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.jdk.CollectionConverters.{CollectionHasAsScala, IterableHasAsJava, MapHasAsScala} @@ -62,14 +65,14 @@ object TypeUtils { case xarr: XdmArray => val list = ListBuffer.empty[Any] var idx = 0 - for (idx <- 0 until xarr.arrayLength()) { + for (idx <- 0 until xarr.arrayLength()) { val value = xarr.get(idx) list += castAsJava(value) } list.toArray case xmap: XdmMap => val map = xmap.asMap() - val jmap = mutable.HashMap.empty[Any,Any] + val jmap = mutable.HashMap.empty[Any, Any] for (key <- map.asScala.keySet) { val value = map.asScala(key) jmap.put(castAsJava(key), castAsJava(value)) @@ -92,14 +95,14 @@ object TypeUtils { case xarr: XdmArray => val list = ListBuffer.empty[Any] var idx = 0 - for (idx <- 0 until xarr.arrayLength()) { + for (idx <- 0 until xarr.arrayLength()) { val value = xarr.get(idx) list += castAsScala(value) } list.toArray case xmap: XdmMap => val map = xmap.asMap() - val jmap = mutable.HashMap.empty[Any,Any] + val jmap = mutable.HashMap.empty[Any, Any] for (key <- map.asScala.keySet) { val value = map.asScala(key) jmap.put(castAsScala(key), castAsScala(value)) @@ -134,7 +137,7 @@ object TypeUtils { MediaType.parse(s"application/vnd.xmlcalabash.$t+xml") } - def castAtomicAs(value: XdmAtomicValue, seqType: Option[SequenceType], context: StaticContext): XdmAtomicValue = { + def castAtomicAs(value: XdmAtomicValue, seqType: Option[SequenceType], context: MinimalStaticContext): XdmAtomicValue = { if (seqType.isEmpty) { return value } @@ -142,15 +145,17 @@ object TypeUtils { castAtomicAs(value, seqType.get.getItemType, context) } - def castAtomicAs(value: XdmAtomicValue, xsdtype: ItemType, context: StaticContext): XdmAtomicValue = { + def castAtomicAs(value: XdmAtomicValue, xsdtype: ItemType, context: MinimalStaticContext): XdmAtomicValue = { + /* if ((xsdtype == ItemType.UNTYPED_ATOMIC) || (xsdtype == ItemType.STRING || (xsdtype == ItemType.ANY_ITEM))) { return value } + */ if (xsdtype == ItemType.QNAME) { val qnamev = value.getPrimitiveTypeName match { - case XProcConstants.xs_string => new XdmAtomicValue(ValueParser.parseQName(value.getStringValue, context)) - case XProcConstants.xs_untypedAtomic => new XdmAtomicValue(ValueParser.parseQName(value.getStringValue, context)) + case XProcConstants.xs_string => new XdmAtomicValue(context.parseQName(value.getStringValue)) + case XProcConstants.xs_untypedAtomic => new XdmAtomicValue(context.parseQName(value.getStringValue)) case XProcConstants.xs_QName => value case _ => throw new RuntimeException(s"Don't know how to convert $value to an xs:QName") @@ -174,53 +179,78 @@ object TypeUtils { throw XProcException.xdBadType(value.getStringValue, xsdtype.toString, location) } case ex: Exception => - throw(ex) + throw (ex) + } + } + + def simpleType(qname: QName): ItemType = { + qname match { + case XProcConstants.xs_anyURI => ItemType.ANY_URI + case XProcConstants.xs_base64Binary => ItemType.BASE64_BINARY + case XProcConstants.xs_boolean => ItemType.BOOLEAN + case XProcConstants.xs_byte => ItemType.BYTE + case XProcConstants.xs_date => ItemType.DATE + case XProcConstants.xs_dateTime => ItemType.DATE_TIME + case XProcConstants.xs_dateTimeStamp => ItemType.DATE_TIME_STAMP + case XProcConstants.xs_dayTimeDuration => ItemType.DAY_TIME_DURATION + case XProcConstants.xs_decimal => ItemType.DECIMAL + case XProcConstants.xs_double => ItemType.DOUBLE + case XProcConstants.xs_duration => ItemType.DURATION + case XProcConstants.xs_ENTITY => ItemType.ENTITY + case XProcConstants.xs_float => ItemType.FLOAT + case XProcConstants.xs_gDay => ItemType.G_DAY + case XProcConstants.xs_gMonth => ItemType.G_MONTH + case XProcConstants.xs_gMonthDay => ItemType.G_MONTH_DAY + case XProcConstants.xs_gYear => ItemType.G_YEAR + case XProcConstants.xs_gYearMonth => ItemType.G_YEAR_MONTH + case XProcConstants.xs_hexBinary => ItemType.HEX_BINARY + case XProcConstants.xs_ID => ItemType.ID + case XProcConstants.xs_IDREF => ItemType.IDREF + case XProcConstants.xs_int => ItemType.INT + case XProcConstants.xs_integer => ItemType.INTEGER + case XProcConstants.xs_language => ItemType.LANGUAGE + case XProcConstants.xs_long => ItemType.LONG + case XProcConstants.xs_name => ItemType.NAME + case XProcConstants.xs_NCName => ItemType.NCNAME + case XProcConstants.xs_negativeInteger => ItemType.NEGATIVE_INTEGER + case XProcConstants.xs_NMTOKEN => ItemType.NMTOKEN + case XProcConstants.xs_nonNegativeInteger => ItemType.NON_NEGATIVE_INTEGER + case XProcConstants.xs_nonPositiveInteger => ItemType.NON_POSITIVE_INTEGER + case XProcConstants.xs_normalizedString => ItemType.NORMALIZED_STRING + case XProcConstants.xs_notation => ItemType.NOTATION + case XProcConstants.xs_positiveInteger => ItemType.POSITIVE_INTEGER + case XProcConstants.xs_QName => ItemType.QNAME + case XProcConstants.xs_short => ItemType.SHORT + case XProcConstants.xs_string => ItemType.STRING + case XProcConstants.xs_time => ItemType.TIME + case XProcConstants.xs_token => ItemType.TOKEN + case XProcConstants.xs_unsignedByte => ItemType.UNSIGNED_BYTE + case XProcConstants.xs_unsignedInt => ItemType.UNSIGNED_INT + case XProcConstants.xs_unsignedLong => ItemType.UNSIGNED_LONG + case XProcConstants.xs_unsignedShort => ItemType.UNSIGNED_SHORT + case XProcConstants.xs_untypedAtomic => ItemType.UNTYPED_ATOMIC + case XProcConstants.xs_yearMonthDuration => ItemType.YEAR_MONTH_DURATION + case XProcConstants.xs_anyAtomicType => ItemType.ANY_ATOMIC_VALUE + case _ => + throw XProcException.xsInvalidSequenceType(qname.getEQName, "Unknown type", None) } } } -class TypeUtils(val processor: Processor, val context: StaticContext) { +class TypeUtils(val processor: Processor, val context: MinimalStaticContext) { + protected val logger: Logger = LoggerFactory.getLogger(this.getClass) private val err_XD0045 = new QName(XProcConstants.ns_err, "XD0045") - def this(config: XMLCalabash, context: StaticContext) = { + def this(config: XMLCalabash, context: MinimalStaticContext) = { this(config.processor, context) } - def this(config: XMLCalabashRuntime, context: StaticContext) = { + + def this(config: XMLCalabashRuntime, context: MinimalStaticContext) = { this(config.processor, context) } - def this(config: XMLCalabash) = { - this(config.processor, new StaticContext(config)) - } - def this(config: XMLCalabashRuntime) = { - this(config.processor, new StaticContext(config)) - } val typeFactory = new ItemTypeFactory(processor) - - // This was added experimentally to handle lists in literal values for include-filter and exclude-filter. - // It was subsequently decided that literal values shouldn't be lists, so this is no longer being used. - // I'm leaving it around for the time being (19 Aug 2018) in case it turns out to be useful somewhere - // else. - def castSequenceAs(value: XdmAtomicValue, xsdtype: Option[QName], occurrence: String, context: StaticContext): XdmValue = { - // Today, we only need to handle a sequence of strings - if (xsdtype.isEmpty || xsdtype.get != XProcConstants.xs_string) { - throw new IllegalArgumentException("Only lists of strings are supported") - } - - val builder = new SequenceBuilder() - val list = builder.parse(value.getStringValue) - val alist = new util.ArrayList[XdmAtomicValue] - - val itype = typeFactory.getAtomicType(XProcConstants.xs_string) - for (item <- list) { - if (item.as != XProcConstants.xs_string) { - throw new IllegalArgumentException("Only lists of strings are supported") - } - alist.add(new XdmAtomicValue(item.item, itype)) - } - - new XdmValue(alist) - } + var types = Option.empty[TypeHierarchy] def parseSequenceType(seqType: Option[String]): Option[SequenceType] = { if (seqType.isDefined) { @@ -242,7 +272,7 @@ class TypeUtils(val processor: Processor, val context: StaticContext) { val mtypere = "^map\\s*\\((.*)\\)\\s*([*+?])?$".r val atypere = "^array\\s*\\((.*)\\)\\s*([*+?])?$".r val ftypere = "^function\\s*\\((.*)\\)\\s*([*+?])?$".r - val itemre = "^item\\s*\\(\\s*\\)\\s*([*+?])?$".r + val itemre = "^item\\s*\\(\\s*\\)\\s*([*+?])?$".r seqType.trim() match { case parensre(body) => parseSequenceType(body) @@ -266,7 +296,7 @@ class TypeUtils(val processor: Processor, val context: StaticContext) { } val tuplere = "^([^,]+),(.*)$".r mbody match { - case tuplere(keyseqtype,itemseqtype) => + case tuplere(keyseqtype, itemseqtype) => val keytype = simpleType(keyseqtype) val itemtype = parseSequenceType(itemseqtype) SequenceType.makeSequenceType(typeFactory.getMapType(keytype, itemtype), cardinality(cardchar)) @@ -299,7 +329,7 @@ class TypeUtils(val processor: Processor, val context: StaticContext) { } val tuplere = "^([^,]+),(.*)$".r mbody match { - case tuplere(keyseqtype,itemseqtype) => + case tuplere(keyseqtype, itemseqtype) => val keytype = ItemType.ANY_ATOMIC_VALUE val itemtype = parseSequenceType(itemseqtype) SequenceType.makeSequenceType(typeFactory.getMapType(keytype, itemtype), cardinality(cardchar)) @@ -312,57 +342,7 @@ class TypeUtils(val processor: Processor, val context: StaticContext) { } private def simpleType(typename: String): ItemType = { - val qname = ValueParser.parseQName(typename, context) - qname match { - case XProcConstants.xs_anyURI => ItemType.ANY_URI - case XProcConstants.xs_base64Binary => ItemType.BASE64_BINARY - case XProcConstants.xs_boolean => ItemType.BOOLEAN - case XProcConstants.xs_byte => ItemType.BYTE - case XProcConstants.xs_date => ItemType.DATE - case XProcConstants.xs_dateTime => ItemType.DATE_TIME - case XProcConstants.xs_dateTimeStamp => ItemType.DATE_TIME_STAMP - case XProcConstants.xs_dayTimeDuration => ItemType.DAY_TIME_DURATION - case XProcConstants.xs_decimal => ItemType.DECIMAL - case XProcConstants.xs_double => ItemType.DOUBLE - case XProcConstants.xs_duration => ItemType.DURATION - case XProcConstants.xs_ENTITY => ItemType.ENTITY - case XProcConstants.xs_float => ItemType.FLOAT - case XProcConstants.xs_gDay => ItemType.G_DAY - case XProcConstants.xs_gMonth => ItemType.G_MONTH - case XProcConstants.xs_gMonthDay => ItemType.G_MONTH_DAY - case XProcConstants.xs_gYear => ItemType.G_YEAR - case XProcConstants.xs_gYearMonth => ItemType.G_YEAR_MONTH - case XProcConstants.xs_hexBinary => ItemType.HEX_BINARY - case XProcConstants.xs_ID => ItemType.ID - case XProcConstants.xs_IDREF => ItemType.IDREF - case XProcConstants.xs_int => ItemType.INT - case XProcConstants.xs_integer => ItemType.INTEGER - case XProcConstants.xs_language => ItemType.LANGUAGE - case XProcConstants.xs_long => ItemType.LONG - case XProcConstants.xs_name => ItemType.NAME - case XProcConstants.xs_NCName => ItemType.NCNAME - case XProcConstants.xs_negativeInteger => ItemType.NEGATIVE_INTEGER - case XProcConstants.xs_NMTOKEN => ItemType.NMTOKEN - case XProcConstants.xs_nonNegativeInteger => ItemType.NON_NEGATIVE_INTEGER - case XProcConstants.xs_nonPositiveInteger => ItemType.NON_POSITIVE_INTEGER - case XProcConstants.xs_normalizedString => ItemType.NORMALIZED_STRING - case XProcConstants.xs_notation => ItemType.NOTATION - case XProcConstants.xs_positiveInteger => ItemType.POSITIVE_INTEGER - case XProcConstants.xs_QName => ItemType.QNAME - case XProcConstants.xs_short => ItemType.SHORT - case XProcConstants.xs_string => ItemType.STRING - case XProcConstants.xs_time => ItemType.TIME - case XProcConstants.xs_token => ItemType.TOKEN - case XProcConstants.xs_unsignedByte => ItemType.UNSIGNED_BYTE - case XProcConstants.xs_unsignedInt => ItemType.UNSIGNED_INT - case XProcConstants.xs_unsignedLong => ItemType.UNSIGNED_LONG - case XProcConstants.xs_unsignedShort => ItemType.UNSIGNED_SHORT - case XProcConstants.xs_untypedAtomic => ItemType.UNTYPED_ATOMIC - case XProcConstants.xs_yearMonthDuration => ItemType.YEAR_MONTH_DURATION - case XProcConstants.xs_anyAtomicType => ItemType.ANY_ATOMIC_VALUE - case _ => - throw XProcException.xsInvalidSequenceType(qname.getClarkName, "Unknown type", context.location) - } + TypeUtils.simpleType(context.parseQName(typename)) } private def cardinality(card: String): OccurrenceIndicator = { @@ -412,4 +392,68 @@ class TypeUtils(val processor: Processor, val context: StaticContext) { val itype = typeFactory.getAtomicType(dtype) new XdmAtomicValue(value, itype) } -} + + def checkType(name: QName, valueMsg: XdmValueItemMessage, seqType: Option[SequenceType], tokenList: Option[List[XdmAtomicValue]]): Unit = { + convertType(name, valueMsg, seqType, tokenList) + } + + def convertType(name: QName, valueMsg: XdmValueItemMessage, seqType: Option[SequenceType], tokenList: Option[List[XdmAtomicValue]]): XdmValueItemMessage = { + if (types.isEmpty) { + types = Some(processor.getUnderlyingConfiguration.getTypeHierarchy) + } + + var value = valueMsg.item + if (seqType.isDefined) { + // Special cases + if (Option(seqType.get.getItemType).isDefined) { + seqType.get.getItemType match { + case ItemType.QNAME => + value.getUnderlyingValue match { + case _: StringValue => + val msg = new XdmValueItemMessage(new XdmAtomicValue(valueMsg.context.parseQName(value.getUnderlyingValue.getStringValue)), valueMsg.metadata, valueMsg.context) + value = msg.item + case _ => + () + } + case ItemType.ANY_URI => + value.getUnderlyingValue match { + case _: StringValue => + val msg = new XdmValueItemMessage(new XdmAtomicValue(new URI(value.getUnderlyingValue.getStringValue)), valueMsg.metadata, valueMsg.context) + value = msg.item + case _ => + () + } + case _ => + () + } + } + + try { + val diag = new RoleDiagnostic(RoleDiagnostic.OPTION, name.toString, 0) + val convseq = types.get.applyFunctionConversionRules(value.getUnderlyingValue, seqType.get.getUnderlyingSequenceType, diag, Loc.NONE) + value = XdmValue.wrap(convseq) + } catch { + case ex: ValidationException => + logger.debug(ex.getMessage) + throw XProcException.xdBadType(name, value.getUnderlyingValue.toString, seqType.get.toString, None) + case ex: XPathException => + logger.debug(ex.getMessage) + throw XProcException.xdBadType(name, value.getUnderlyingValue.toString, seqType.get.toString, None) + case ex: Exception => + logger.debug(ex.getMessage) + throw ex + } + } + + if (tokenList.isDefined) { + if (value ne XdmEmptySequence.getInstance()) { + val matched = tokenList.get find { _ == value } + if (matched.isEmpty) { + throw XProcException.xdBadValue(value.toString, None) + } + } + } + + new XdmValueItemMessage(value, valueMsg.metadata, valueMsg.context) + } +} \ No newline at end of file diff --git a/src/main/scala/com/xmlcalabash/util/URIUtils.scala b/src/main/scala/com/xmlcalabash/util/URIUtils.scala index 7035269..3b343f2 100644 --- a/src/main/scala/com/xmlcalabash/util/URIUtils.scala +++ b/src/main/scala/com/xmlcalabash/util/URIUtils.scala @@ -60,7 +60,7 @@ object URIUtils { // don't double-escape %-escaped chars! // FIXME: This should be more general, but Windows seems to be the only problem - // and I'm oto lazy to look up how to dyanmically escape "\" + // and I'm to lazy to look up how to dyanmically escape "\" val filesep = System.getProperty("file.separator") val adjsrc = if ("\\" == filesep) { @@ -70,7 +70,7 @@ object URIUtils { } var encoded = "" - val bytes = src.getBytes("UTF-8") + val bytes = adjsrc.getBytes("UTF-8") for (ch <- bytes) { if (okChars.indexOf(ch) >= 0) { encoded += ch.toChar diff --git a/src/main/scala/com/xmlcalabash/util/Urify.scala b/src/main/scala/com/xmlcalabash/util/Urify.scala index f8c7149..13362c0 100644 --- a/src/main/scala/com/xmlcalabash/util/Urify.scala +++ b/src/main/scala/com/xmlcalabash/util/Urify.scala @@ -44,6 +44,10 @@ object Urify { urify(filestr, Some(basedir)) } + def urify(filestr: String, basedir: URI): String = { + urify(filestr, Some(basedir.toString)) + } + def urify(filestr: String, basedir: Option[String]): String = { val filepath = new Urify(filestr, basedir) if (!filepath.hierarchical || (filepath.scheme.isDefined && filepath.absolute)) { diff --git a/src/main/scala/com/xmlcalabash/util/VoidLocation.scala b/src/main/scala/com/xmlcalabash/util/VoidLocation.scala index f037e60..1f9361c 100644 --- a/src/main/scala/com/xmlcalabash/util/VoidLocation.scala +++ b/src/main/scala/com/xmlcalabash/util/VoidLocation.scala @@ -9,7 +9,7 @@ object VoidLocation { def instance(): VoidLocation = loc } -class VoidLocation extends Location { +class VoidLocation private extends Location { override def getSystemId: String = null override def getPublicId: String = null diff --git a/src/main/scala/com/xmlcalabash/util/XProcVarValue.scala b/src/main/scala/com/xmlcalabash/util/XProcVarValue.scala index b999298..143d05d 100644 --- a/src/main/scala/com/xmlcalabash/util/XProcVarValue.scala +++ b/src/main/scala/com/xmlcalabash/util/XProcVarValue.scala @@ -1,9 +1,8 @@ package com.xmlcalabash.util -import com.xmlcalabash.runtime.StaticContext import net.sf.saxon.s9api.XdmValue -class XProcVarValue(val value: XdmValue, val context: StaticContext) { +class XProcVarValue(val value: XdmValue, val context: MinimalStaticContext) { def getStringValue: String = { val iter = value.iterator() var s = "" diff --git a/src/main/scala/com/xmlcalabash/util/XdmLocation.scala b/src/main/scala/com/xmlcalabash/util/XdmLocation.scala new file mode 100644 index 0000000..e264608 --- /dev/null +++ b/src/main/scala/com/xmlcalabash/util/XdmLocation.scala @@ -0,0 +1,125 @@ +package com.xmlcalabash.util + +import com.jafpl.graph.Location +import com.xmlcalabash.exceptions.XProcException +import net.sf.saxon.s9api.{Axis, XdmNode, XdmNodeKind} + +import java.net.{URI, URISyntaxException} +import scala.xml.SAXParseException + +object XdmLocation { + // Can't use a constructor for this because node.getBaseURI might fail + def from(node: XdmNode): Option[XdmLocation] = { + val uristr = node.getUnderlyingNode.getBaseURI + if (Option(uristr).isEmpty) { + None + } else { + try { + val buri = new URI(uristr) + Some(new XdmLocation(node)) + } catch { + case _: URISyntaxException => + throw XProcException.xdInvalidURI(uristr, None) + case ex: Exception => + throw ex + } + } + } +} + +class XdmLocation private(private val puri: String, private val pline: Int, private val pcol: Int) extends Location { + private var _uri: Option[String] = Some(puri) + private var _line = pline + private var _col = pcol + + private def this(node: XdmNode) = { + this(Urify.urify(node.getBaseURI.toString), node.getLineNumber, node.getColumnNumber) + + // Special case: if there's a preceding sibling PI that contains the original + // location, decode that and use it. + var pi = Option.empty[XdmNode] + var found = false + val iter = node.axisIterator(Axis.PRECEDING_SIBLING) + while (iter.hasNext) { + val pnode = iter.next() + if (!found) { + pnode.getNodeKind match { + case XdmNodeKind.TEXT => + if (pnode.getStringValue.trim != "") { + found = true + } + case XdmNodeKind.PROCESSING_INSTRUCTION => + if (pnode.getNodeName.getLocalName == "_xmlcalabash") { + pi = Some(pnode) + found = true + } + case _ => found = true + } + } + } + + if (found && pi.isDefined) { + val str = pi.get.getStringValue + val uripatn = ".*uri=\"([^\"]+)\".*".r + val linepatn = ".*line=\"(\\d+)\".*".r + val colpatn = ".*column=\"(\\d+)\".*".r + + str match { + case uripatn(uri) => + _uri = Some(uri) + case _ => + _uri = None + } + + str match { + case linepatn(line) => + _line = line.toInt + case _ => + _line = -1 + } + + str match { + case colpatn(col) => + _col = col.toInt + case _ => + _col = -1 + } + } + } + + def this(ex: SAXParseException) = { + this(ex.getSystemId, ex.getLineNumber, ex.getColumnNumber) + } + + override def uri: Option[String] = _uri + + override def line: Option[Long] = { + if (_line > 0) { + Some(_line.toLong) + } else { + None + } + } + + override def column: Option[Long] = { + if (_col > 0) { + Some(_col.toLong) + } else { + None + } + } + + override def toString: String = { + var str = "" + if (_uri.isDefined) { + str += _uri.get + } + if (line.isDefined) { + str += ":" + line.get.toString + } + if (column.isDefined) { + str += ":" + column.get.toString + } + str + } +} diff --git a/src/main/scala/com/xmlcalabash/util/XmlItemComparator.scala b/src/main/scala/com/xmlcalabash/util/XmlItemComparator.scala index 439fc54..26c9983 100644 --- a/src/main/scala/com/xmlcalabash/util/XmlItemComparator.scala +++ b/src/main/scala/com/xmlcalabash/util/XmlItemComparator.scala @@ -5,13 +5,13 @@ import com.jafpl.util.ItemComparator import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException import com.xmlcalabash.messages.XdmValueItemMessage -import com.xmlcalabash.model.xml.ForUntil +import com.xmlcalabash.model.xxml.{XForUntil, XStaticContext} import com.xmlcalabash.runtime.{DynamicContext, StaticContext, XProcMetadata, XProcXPathExpression} import net.sf.saxon.s9api.{XdmNode, XdmValue} import scala.collection.mutable -class XmlItemComparator(config: XMLCalabash, comparator: String, maxIterations: Long, art: ForUntil) extends ItemComparator { +class XmlItemComparator(config: XMLCalabash, comparator: String, maxIterations: Long, art: XForUntil) extends ItemComparator { private var count = 0L override def areTheSame(a: Any, b: Any): Boolean = { @@ -38,7 +38,7 @@ class XmlItemComparator(config: XMLCalabash, comparator: String, maxIterations: } private def sameNode(anode: XdmNode, bnode: XdmNode): Boolean = { - val context = new StaticContext(config) + val context = new XStaticContext() val expr = new XProcXPathExpression(context, comparator) val bindingsMap = mutable.HashMap.empty[String, Message] val amsg = new XdmValueItemMessage(anode, XProcMetadata.XML, context) @@ -47,7 +47,7 @@ class XmlItemComparator(config: XMLCalabash, comparator: String, maxIterations: bindingsMap.put("{}b", bmsg) var same = false - val dynamicContext = new DynamicContext(Some(art)) + val dynamicContext = new DynamicContext(art) DynamicContext.withContext(dynamicContext) { val exeval = config.expressionEvaluator.newInstance() same = exeval.booleanValue(expr, List(amsg), bindingsMap.toMap, None) diff --git a/src/main/scala/com/xmlcalabash/util/XmlItemTester.scala b/src/main/scala/com/xmlcalabash/util/XmlItemTester.scala index c96d048..86fc669 100644 --- a/src/main/scala/com/xmlcalabash/util/XmlItemTester.scala +++ b/src/main/scala/com/xmlcalabash/util/XmlItemTester.scala @@ -2,12 +2,11 @@ package com.xmlcalabash.util import com.jafpl.messages.Message import com.jafpl.util.ItemTester -import com.xmlcalabash.XMLCalabash import com.xmlcalabash.exceptions.XProcException -import com.xmlcalabash.model.xml.ForWhile -import com.xmlcalabash.runtime.{DynamicContext, StaticContext, XProcXPathExpression} +import com.xmlcalabash.model.xxml.{XForWhile, XStaticContext} +import com.xmlcalabash.runtime.{DynamicContext, SaxonExpressionEvaluator, XMLCalabashRuntime, XProcXPathExpression} -class XmlItemTester(config: XMLCalabash, comparator: String, maxIterations: Long, art: ForWhile) extends ItemTester { +class XmlItemTester(runtime: XMLCalabashRuntime, comparator: String, maxIterations: Long, art: XForWhile) extends ItemTester { private var count = 0L override def test(item: List[Message], bindings: Map[String, Message]): Boolean = { @@ -16,13 +15,12 @@ class XmlItemTester(config: XMLCalabash, comparator: String, maxIterations: Long } count += 1 - val context = new StaticContext(config) - val expr = new XProcXPathExpression(context, comparator) + val expr = new XProcXPathExpression(art.staticContext, comparator) var pass = false - val dynamicContext = new DynamicContext(Some(art)) + val dynamicContext = new DynamicContext(runtime, art) DynamicContext.withContext(dynamicContext) { - val exeval = config.expressionEvaluator.newInstance() + val exeval = runtime.expressionEvaluator.newInstance() pass = exeval.booleanValue(expr, item, bindings, None) } diff --git a/src/test/resources/com.xmlcalabash.properties b/src/test/resources/com.xmlcalabash.properties deleted file mode 100644 index e026d2a..0000000 --- a/src/test/resources/com.xmlcalabash.properties +++ /dev/null @@ -1,2 +0,0 @@ -cx = namespace http://xmlcalabash.com/ns/extensions -com.xmlcalabash.steps.StepOutputTest = step cx:step-output-test diff --git a/src/test/resources/com.xmlcalabash.settings b/src/test/resources/com.xmlcalabash.settings new file mode 100644 index 0000000..feaf5f3 --- /dev/null +++ b/src/test/resources/com.xmlcalabash.settings @@ -0,0 +1,2 @@ +namespace http://xmlcalabash.com/ns/extensions = cx +step cx:step-output-test = com.xmlcalabash.steps.StepOutputTest diff --git a/src/test/resources/extension-tests.txt b/src/test/resources/extension-tests.txt index 2c255ee..a7e4aae 100644 --- a/src/test/resources/extension-tests.txt +++ b/src/test/resources/extension-tests.txt @@ -1,6 +1,3 @@ -collection-attr-001.xml -collection-attr-002.xml -collection-attr-003.xml cx-base64-encode.xml cx-loop-001.xml cx-loop-002.xml diff --git a/src/test/resources/extension-tests/tests/collection-attr-001.xml b/src/test/resources/extension-tests/tests/collection-attr-001.xml deleted file mode 100644 index 5357104..0000000 --- a/src/test/resources/extension-tests/tests/collection-attr-001.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The pipeline root is not c:result - - - The result is not 2 - - - - - - diff --git a/src/test/resources/extension-tests/tests/collection-attr-002.xml b/src/test/resources/extension-tests/tests/collection-attr-002.xml deleted file mode 100644 index 9211822..0000000 --- a/src/test/resources/extension-tests/tests/collection-attr-002.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/resources/extension-tests/tests/collection-attr-003.xml b/src/test/resources/extension-tests/tests/collection-attr-003.xml deleted file mode 100644 index 97649e6..0000000 --- a/src/test/resources/extension-tests/tests/collection-attr-003.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - fail - - - - - fail - - - - - - diff --git a/src/test/scala/com/xmlcalabash/drivers/ParserTest.scala b/src/test/scala/com/xmlcalabash/drivers/ParserTest.scala new file mode 100644 index 0000000..1a0958d --- /dev/null +++ b/src/test/scala/com/xmlcalabash/drivers/ParserTest.scala @@ -0,0 +1,75 @@ +package com.xmlcalabash.drivers + +import com.xmlcalabash.XMLCalabash +import com.xmlcalabash.config.DocumentRequest +import com.xmlcalabash.exceptions.XProcException +import com.xmlcalabash.messages.XdmValueItemMessage +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.model.xxml.{XOption, XParser} +import com.xmlcalabash.runtime.{StaticContext, XProcMetadata} +import com.xmlcalabash.testing.TestRunner +import com.xmlcalabash.util.{DefaultErrorExplanation, MediaType, S9Api} +import net.sf.saxon.s9api.{Processor, QName, Serializer, XdmAtomicValue, XdmDestination, XdmValue} +import org.slf4j.LoggerFactory + +import java.io.{BufferedReader, File, FileReader, PrintWriter} +import java.net.URI +import java.nio.file.{Files, Paths} +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.jdk.javaapi.CollectionConverters.asJava + +object ParserTest extends App { + private val config: XMLCalabash = XMLCalabash.newInstance() + private val explainer = new DefaultErrorExplanation() + + config.args.config(XProcConstants.cc_graph.getEQName, "pipeline") + config.args.config(XProcConstants.cc_graph.getEQName, "graph") + config.args.config(XProcConstants.cc_graph.getEQName, "graph-open") + config.args.option("limit", 10) + + config.configure() + + val xparser = new XParser(config) + try { + //val decl = xparser.loadDeclareStep(new URI("file:///Users/ndw/Projects/xproc/gradle-plugin/examples/json/countdown.xpl")) + val decl = xparser.loadDeclareStep(new URI("file:///Users/ndw/Projects/xproc/meerschaum/pipe.xpl")) + if (decl.exceptions.isEmpty) { + println("SUCCESS") + println(decl.dump) + + val runtime = decl.runtime() + //runtime.run() + //println(decl.dump) + } else { + println(s"FAIL: ${decl.exceptions.length}") + var count = 0 + for (ex <- decl.exceptions) { + count += 1 + ex match { + case xp: XProcException => + println(s"${count}: ${xp.location.getOrElse("")} ${explain(ex)}") + case _ => + println(s"${count}: ${explain(ex)}") + } + } + } + } catch { + case ex: Exception => + println(explain(ex)) + var count = 0 + for (ex <- xparser.exceptions) { + count += 1 + println(s"${count}: ${explain(ex)}") + } + } + + private def explain(ex: Exception): String = { + ex match { + case xp: XProcException => + explainer.message(xp.code, xp.variant, xp.details) + case _ => + ex.getMessage + } + } +} diff --git a/src/test/scala/com/xmlcalabash/steps/StepOutputTest.scala b/src/test/scala/com/xmlcalabash/steps/StepOutputTest.scala index 2073211..13d27a1 100644 --- a/src/test/scala/com/xmlcalabash/steps/StepOutputTest.scala +++ b/src/test/scala/com/xmlcalabash/steps/StepOutputTest.scala @@ -1,14 +1,11 @@ package com.xmlcalabash.steps -import java.io.ByteArrayInputStream - -import com.xmlcalabash.model.xml.XMLContext import com.xmlcalabash.model.util.{SaxonTreeBuilder, XProcConstants} import com.xmlcalabash.runtime.{StaticContext, XProcMetadata, XmlPortSpecification} -import com.xmlcalabash.util.MediaType -import com.xmlcalabash.util.TypeUtils.castAsXml +import com.xmlcalabash.util.{MediaType, MinimalStaticContext} import net.sf.saxon.s9api.{QName, XdmAtomicValue, XdmMap, XdmValue} -import net.sf.saxon.value.AtomicValue + +import java.io.ByteArrayInputStream class StepOutputTest() extends DefaultXmlStep { private val _content_type = XProcConstants._content_type @@ -17,7 +14,7 @@ class StepOutputTest() extends DefaultXmlStep { override def inputSpec: XmlPortSpecification = XmlPortSpecification.NONE override def outputSpec: XmlPortSpecification = XmlPortSpecification.ANYRESULT - override def run(context: StaticContext): Unit = { + override def run(context: MinimalStaticContext): Unit = { val contentType = MediaType.parse(stringBinding(_content_type)) val resultType = stringBinding(_result_type) val metadata = new XProcMetadata(contentType, Map.empty[QName,XdmValue]) diff --git a/src/test/scala/com/xmlcalabash/test/XStaticContextSpec.scala b/src/test/scala/com/xmlcalabash/test/XStaticContextSpec.scala new file mode 100644 index 0000000..7390a34 --- /dev/null +++ b/src/test/scala/com/xmlcalabash/test/XStaticContextSpec.scala @@ -0,0 +1,107 @@ +package com.xmlcalabash.test + +import com.jafpl.graph.Location +import com.xmlcalabash.model.util.XProcConstants +import com.xmlcalabash.model.xxml.XMLStaticContext +import net.sf.saxon.s9api.QName +import org.scalatest.flatspec.AnyFlatSpec + +import scala.collection.mutable + +object VoidLocation extends Location { + override def uri: Option[String] = None + override def line: Option[Long] = None + override def column: Option[Long] = None +} + +class XStaticContextSpec extends AnyFlatSpec { + private val nsmap = mutable.HashMap.empty[String,String] + + nsmap.put("p", XProcConstants.ns_p) + nsmap.put("cx", XProcConstants.ns_cx) + + private val staticContext = new XMLStaticContext(new QName("", "irrelevant"), VoidLocation, nsmap.toMap) + + "p:foo " should "be a valid QName" in { + val qname = staticContext.parseQName("p:foo") + assert(qname.getPrefix == "p") + assert(qname.getNamespaceURI == XProcConstants.ns_p) + assert(qname.getLocalName == "foo") + } + + "foo " should "be a valid QName" in { + val qname = staticContext.parseQName("foo") + assert(qname.getPrefix == "") + assert(qname.getNamespaceURI == "") + assert(qname.getLocalName == "foo") + } + + "Q{http://example.com/}foo " should "be a valid QName" in { + val qname = staticContext.parseQName("Q{http://example.com/}foo") + assert(qname.getPrefix == "") + assert(qname.getNamespaceURI == "http://example.com/") + assert(qname.getLocalName == "foo") + } + + ":foo " should " throw an exception" in { + try { + staticContext.parseQName(":foo") + fail() + } catch { + case _: Exception => + () + } + } + + "xyz:foo " should " throw an exception" in { + try { + staticContext.parseQName("xyz:foo") + fail() + } catch { + case _: Exception => + () + } + } + + "Q{foo " should " throw an exception" in { + try { + staticContext.parseQName("Q{foo") + fail() + } catch { + case _: Exception => + () + } + } + + "3 " should " throw an exception" in { + try { + staticContext.parseQName("3") + fail() + } catch { + case _: Exception => + () + } + } + + "p:3 " should " throw an exception" in { + try { + staticContext.parseQName("p:3") + fail() + } catch { + case _: Exception => + () + } + } + + "p:foo:bar " should " throw an exception" in { + try { + staticContext.parseQName("p:foo:bar") + fail() + } catch { + case _: Exception => + () + } + } + + +}