func CompileSingleNode(node xml.Node) (step CompiledStep) { switch node.NodeType() { case xml.XML_ELEMENT_NODE: ns := node.Namespace() // element, extension namespace = extension if ns == XSLT_NAMESPACE { // element, XSLT namespace = instruction switch node.Name() { case "variable": step = &Variable{Node: node} case "param", "with-param": step = &Variable{Node: node} default: step = &XsltInstruction{Name: node.Name(), Node: node} } } else { // element other namespace = LRE step = &LiteralResultElement{Node: node} } // text, CDATA node case xml.XML_TEXT_NODE, xml.XML_CDATA_SECTION_NODE: if !IsBlank(node) { step = &TextOutput{Content: node.Content()} } } return }
func (style *Stylesheet) populateKeys(node xml.Node, context *ExecutionContext) { for _, key := range style.Keys { //see if the current node matches matches := CompileMatch(key.match, nil) hasMatch := false for _, m := range matches { if m.EvalMatch(node, "", context) { hasMatch = true break } } if !hasMatch { continue } lookupkey, _ := node.EvalXPath(key.use, context) lookup := "" switch lk := lookupkey.(type) { case []xml.Node: if len(lk) == 0 { continue } lookup = lk[0].String() case string: lookup = lk default: lookup = fmt.Sprintf("%v", lk) } key.nodes[lookup] = append(key.nodes[lookup], node) } children := context.ChildrenOf(node) for _, cur := range children { style.populateKeys(cur, context) } }
func (e *LiteralResultElement) Compile(node xml.Node) { for cur := node.FirstChild(); cur != nil; cur = cur.NextSibling() { res := CompileSingleNode(cur) if res != nil { res.Compile(cur) e.Children = append(e.Children, res) } } }
func (template *Template) CompileContent(node xml.Node) { //parse the content and register the match pattern for cur := node.FirstChild(); cur != nil; cur = cur.NextSibling() { res := CompileSingleNode(cur) if res != nil { res.Compile(cur) template.AddChild(res) } } }
// Compile the variable. // // TODO: compile the XPath expression and determine if it is a constant func (i *Variable) Compile(node xml.Node) { i.Name = i.Node.Attr("name") for cur := node.FirstChild(); cur != nil; cur = cur.NextSibling() { res := CompileSingleNode(cur) if res != nil { res.Compile(cur) i.Children = append(i.Children, res) } } }
// Determine the default namespace currently defined in scope func (context *ExecutionContext) DefaultNamespace(node xml.Node) string { //get the list of in-scope namespaces // any with a null prefix? return that decl := node.DeclaredNamespaces() for _, d := range decl { if d.Prefix == "" { return d.Uri } } return "" }
// ChildrenOf returns the node children, ignoring any whitespace-only text nodes that // are stripped by strip-space or xml:space func (context *ExecutionContext) ChildrenOf(node xml.Node) (children []xml.Node) { for cur := node.FirstChild(); cur != nil; cur = cur.NextSibling() { //don't count stripped nodes if context.ShouldStrip(cur) { continue } children = append(children, cur) } return }
// Compile the instruction. // // TODO: we should validate the structure during this step func (i *XsltInstruction) Compile(node xml.Node) { for cur := node.FirstChild(); cur != nil; cur = cur.NextSibling() { res := CompileSingleNode(cur) if cur.Name() == "sort" && cur.Namespace() == XSLT_NAMESPACE { i.sorting = append(i.sorting, compileSortFunction(res.(*XsltInstruction))) continue } if res != nil { res.Compile(cur) i.Children = append(i.Children, res) } } }
// Propogate namespaces to the root of the output document func (context *ExecutionContext) DeclareStylesheetNamespacesIfRoot(node xml.Node) { if context.OutputNode.NodeType() != xml.XML_DOCUMENT_NODE { return } //add all namespace declarations to r for uri, prefix := range context.Style.NamespaceMapping { if uri != XSLT_NAMESPACE { //these don't actually change if there is no alias _, uri = ResolveAlias(context.Style, prefix, uri) if !context.Style.IsExcluded(prefix) { node.DeclareNamespace(prefix, uri) } } } }
func (context *ExecutionContext) EvalXPathAsNodeset(xmlNode xml.Node, data interface{}) (result xml.Nodeset, err error) { _, err = context.EvalXPath(xmlNode, data) if err != nil { return nil, err } nodePtrs, err := context.XPathContext.ResultAsNodeset() if err != nil { return nil, err } var output xml.Nodeset for _, nodePtr := range nodePtrs { output = append(output, xml.NewNode(nodePtr, xmlNode.MyDocument())) } result = output return }
func (context *ExecutionContext) EvalXPath(xmlNode xml.Node, data interface{}) (result interface{}, err error) { switch data := data.(type) { case string: if xpathExpr := xpath.Compile(data); xpathExpr != nil { defer xpathExpr.Free() result, err = context.EvalXPath(xmlNode, xpathExpr) } else { err = errors.New("cannot compile xpath: " + data) } case []byte: result, err = context.EvalXPath(xmlNode, string(data)) case *xpath.Expression: xpathCtx := context.XPathContext xpathCtx.SetResolver(context) err := xpathCtx.Evaluate(xmlNode.NodePtr(), data) if err != nil { return nil, err } rt := xpathCtx.ReturnType() switch rt { case xpath.XPATH_NODESET, xpath.XPATH_XSLT_TREE: nodePtrs, err := xpathCtx.ResultAsNodeset() if err != nil { return nil, err } var output []xml.Node for _, nodePtr := range nodePtrs { output = append(output, xml.NewNode(nodePtr, xmlNode.MyDocument())) } result = output case xpath.XPATH_NUMBER: result, err = xpathCtx.ResultAsNumber() case xpath.XPATH_BOOLEAN: result, err = xpathCtx.ResultAsBoolean() default: result, err = xpathCtx.ResultAsString() } default: err = errors.New("Strange type passed to ExecutionContext.EvalXPath") } return }
// ShouldStrip evaluates the strip-space, preserve-space, and xml:space rules // and returns true if a node is a whitespace-only text node that should // be stripped. func (context *ExecutionContext) ShouldStrip(xmlNode xml.Node) bool { if xmlNode.NodeType() != xml.XML_TEXT_NODE { return false } if !IsBlank(xmlNode) { return false } //do we have a match in strip-space? elem := xmlNode.Parent().Name() ns := xmlNode.Parent().Namespace() for _, pat := range context.Style.StripSpace { if pat == elem { return true } if pat == "*" { return true } if strings.Contains(pat, ":") { uri, name := context.ResolveQName(pat) if uri == ns { if name == elem || name == "*" { return true } } } } //do we have a match in preserve-space? //resolve conflicts by priority (QName, ns:*, *) //return a value return false }
// Returns true if the node is a whitespace-only text node func IsBlank(xmlnode xml.Node) bool { if xmlnode.NodeType() == xml.XML_TEXT_NODE || xmlnode.NodeType() == xml.XML_CDATA_SECTION_NODE { content := xmlnode.Content() if content == "" || strings.TrimSpace(content) == "" { return true } } return false }
func (context *ExecutionContext) UseCDataSection(node xml.Node) bool { if node.NodeType() != xml.XML_ELEMENT_NODE { return false } name := node.Name() ns := node.Namespace() for _, el := range context.Style.CDataElements { if el == name { return true } uri, elname := context.ResolveQName(el) if uri == ns && name == elname { return true } } return false }
// ParseTemplate parses and compiles the xsl:template elements. func (style *Stylesheet) ParseTemplate(node xml.Node) { //add to template list of stylesheet //parse mode, match, name, priority mode := node.Attr("mode") name := node.Attr("name") match := node.Attr("match") priority := node.Attr("priority") p := 0.0 if priority != "" { p, _ = strconv.ParseFloat(priority, 64) } // TODO: validate the name (duplicate should raise error) template := &Template{Match: match, Mode: mode, Name: name, Priority: p, Node: node} template.CompileContent(node) // compile pattern style.compilePattern(template, priority) }
func (style *Stylesheet) processDefaultRule(node xml.Node, context *ExecutionContext) { //default for DOCUMENT, ELEMENT children := context.ChildrenOf(node) total := len(children) for i, cur := range children { context.XPathContext.SetContextPosition(i+1, total) style.processNode(cur, context, nil) } //default for CDATA, TEXT, ATTR is copy as text if node.NodeType() == xml.XML_TEXT_NODE { if context.ShouldStrip(node) { return } if context.UseCDataSection(context.OutputNode) { r := context.Output.CreateCDataNode(node.Content()) context.OutputNode.AddChild(r) } else { r := context.Output.CreateTextNode(node.Content()) context.OutputNode.AddChild(r) } } //default for namespace declaration is copy to output document }
func (i *XsltInstruction) numbering(node xml.Node, context *ExecutionContext) { //level level := i.Node.Attr("level") if level == "" { level = "single" } //count count := i.Node.Attr("count") if count == "" { //TODO: qname (should match NS as well count = node.Name() } //from from := i.Node.Attr("from") //value valattr := i.Node.Attr("value") //format format := i.Node.Attr("format") if format == "" { format = "1" } //lang //letter-value //grouping-seperator //grouping-size var numbers []int //if value, just use that! if valattr != "" { v, _ := node.EvalXPath(valattr, context) if v == nil { numbers = append(numbers, 0) } else { numbers = append(numbers, int(v.(float64))) } } else { target := findTarget(node, count) v := countNodes(level, target, count, from) numbers = append(numbers, v) if level == "multiple" { for cur := target.Parent(); cur != nil; cur = cur.Parent() { v = countNodes(level, cur, count, from) if v > 0 { numbers = append(numbers, v) } } if len(numbers) > 1 { for i, j := 0, len(numbers)-1; i < j; i, j = i+1, j-1 { numbers[i], numbers[j] = numbers[j], numbers[i] } } } } // level = multiple // count preceding siblings AT EACH LEVEL // format using the format string outtxt := formatNumbers(numbers, format) r := context.Output.CreateTextNode(outtxt) context.OutputNode.AddChild(r) }
func (style *Stylesheet) RegisterAttributeSet(node xml.Node) { name := node.Attr("name") res := CompileSingleNode(node) res.Compile(node) style.AttributeSets[name] = res }
func (style *Stylesheet) RegisterGlobalVariable(node xml.Node) { name := node.Attr("name") _var := CompileSingleNode(node).(*Variable) _var.Compile(node) style.Variables[name] = _var }
// Returns true if the node is in the XSLT namespace func IsXsltName(xmlnode xml.Node, name string) bool { if xmlnode.Name() == name && xmlnode.Namespace() == XSLT_NAMESPACE { return true } return false }
// Returns true if the node matches the pattern func (m *CompiledMatch) EvalMatch(node xml.Node, mode string, context *ExecutionContext) bool { cur := node //false if wrong mode // #all is an XSLT 2.0 feature if m.Template != nil && mode != m.Template.Mode && m.Template.Mode != "#all" { return false } for i, step := range m.Steps { switch step.Op { case OP_END: return true case OP_ROOT: if cur.NodeType() != xml.XML_DOCUMENT_NODE { return false } case OP_ELEM: if cur.NodeType() != xml.XML_ELEMENT_NODE { return false } if step.Value != cur.Name() && step.Value != "*" { return false } case OP_NS: uri := "" // m.Template.Node if m.Template != nil { uri = context.LookupNamespace(step.Value, m.Template.Node) } else { uri = context.LookupNamespace(step.Value, nil) } if uri != cur.Namespace() { return false } case OP_ATTR: if cur.NodeType() != xml.XML_ATTRIBUTE_NODE { return false } if step.Value != cur.Name() && step.Value != "*" { return false } case OP_TEXT: if cur.NodeType() != xml.XML_TEXT_NODE && cur.NodeType() != xml.XML_CDATA_SECTION_NODE { return false } case OP_COMMENT: if cur.NodeType() != xml.XML_COMMENT_NODE { return false } case OP_ALL: if cur.NodeType() != xml.XML_ELEMENT_NODE { return false } case OP_PI: if cur.NodeType() != xml.XML_PI_NODE { return false } case OP_NODE: switch cur.NodeType() { case xml.XML_ELEMENT_NODE, xml.XML_CDATA_SECTION_NODE, xml.XML_TEXT_NODE, xml.XML_COMMENT_NODE, xml.XML_PI_NODE: // matches any of these node types default: return false } case OP_PARENT: cur = cur.Parent() if cur == nil { return false } case OP_ANCESTOR: next := m.Steps[i+1] if next.Op != OP_ELEM { return false } for { cur = cur.Parent() if cur == nil { return false } if next.Value == cur.Name() { break } } case OP_PREDICATE: // see test REC/5.2-16 // see test REC/5.2-22 evalFull := true if context != nil { prev := m.Steps[i-1] if prev.Op == OP_PREDICATE { prev = m.Steps[i-2] } if prev.Op == OP_ELEM || prev.Op == OP_ALL { parent := cur.Parent() sibs := context.ChildrenOf(parent) var clen, pos int for _, n := range sibs { if n.NodePtr() == cur.NodePtr() { pos = clen + 1 clen = clen + 1 } else { if n.NodeType() == xml.XML_ELEMENT_NODE { if n.Name() == cur.Name() || prev.Op == OP_ALL { clen = clen + 1 } } } } if step.Value == "last()" { if pos != clen { return false } } //eval predicate should do special number handling postest, err := strconv.Atoi(step.Value) if err == nil { if pos != postest { return false } } opos, olen := context.XPathContext.GetContextPosition() context.XPathContext.SetContextPosition(pos, clen) result := cur.EvalXPathAsBoolean(step.Value, context) context.XPathContext.SetContextPosition(opos, olen) if result == false { return false } evalFull = false } } if evalFull { //if we made it this far, fall back to the more expensive option of evaluating // the entire pattern globally //TODO: cache results on first run for given document xp := m.pattern if m.pattern[0] != '/' { xp = "//" + m.pattern } e := xpath.Compile(xp) o, err := node.Search(e) if err != nil { //fmt.Println("ERROR",err) } for _, n := range o { if cur.NodePtr() == n.NodePtr() { return true } } return false } case OP_ID: //TODO: fix lexer to only put literal inside step value val := strings.Trim(step.Value, "()\"'") id := cur.MyDocument().NodeById(val) if id == nil || node.NodePtr() != id.NodePtr() { return false } case OP_KEY: // TODO: make this robust if context != nil { val := strings.Trim(step.Value, "()") v := strings.Split(val, ",") keyname := strings.Trim(v[0], "\"'") keyval := strings.Trim(v[1], "\"'") key, _ := context.Style.Keys[keyname] if key != nil { o, _ := key.nodes[keyval] for _, n := range o { if cur.NodePtr() == n.NodePtr() { return true } } } } return false default: return false } } //in theory, OP_END means we never reach here // in practice, we can generate match patterns // that are missing OP_END due to how we handle OP_OR return true }
func (i *XsltInstruction) copyToOutput(node xml.Node, context *ExecutionContext, recursive bool) { switch node.NodeType() { case xml.XML_TEXT_NODE: if context.UseCDataSection(context.OutputNode) { r := context.Output.CreateCDataNode(node.Content()) context.OutputNode.AddChild(r) } else { r := context.Output.CreateTextNode(node.Content()) context.OutputNode.AddChild(r) } case xml.XML_ATTRIBUTE_NODE: aname := node.Name() ahref := node.Namespace() val := node.Content() if ahref == "" { context.OutputNode.SetAttr(aname, val) } else { context.OutputNode.SetNsAttr(ahref, aname, val) } case xml.XML_COMMENT_NODE: r := context.Output.CreateCommentNode(node.Content()) context.OutputNode.AddChild(r) case xml.XML_PI_NODE: name := node.Attr("name") r := context.Output.CreatePINode(name, node.Content()) context.OutputNode.AddChild(r) case xml.XML_NAMESPACE_DECL: //in theory this should work //in practice it's a little complicated due to the fact //that namespace declarations don't map to the node type //very well //will need to revisit //context.OutputNode.DeclareNamespace(node.Name(), node.Content()) case xml.XML_ELEMENT_NODE: aname := node.Name() r := context.Output.CreateElementNode(aname) context.OutputNode.AddChild(r) ns := node.Namespace() if ns != "" { //TODO: search through namespaces in-scope prefix, _ := context.Style.NamespaceMapping[ns] r.SetNamespace(prefix, ns) } else { //may need to explicitly reset to empty namespace def := context.DefaultNamespace(context.OutputNode) if def != "" { r.SetNamespace("", "") } } //copy namespace declarations for _, decl := range node.DeclaredNamespaces() { r.DeclareNamespace(decl.Prefix, decl.Uri) } old := context.OutputNode context.OutputNode = r if recursive { //copy attributes for _, attr := range node.Attributes() { i.copyToOutput(attr, context, recursive) } for cur := node.FirstChild(); cur != nil; cur = cur.NextSibling() { i.copyToOutput(cur, context, recursive) } } context.OutputNode = old case xml.XML_DOCUMENT_NODE: if recursive { for cur := node.FirstChild(); cur != nil; cur = cur.NextSibling() { i.copyToOutput(cur, context, recursive) } } } }
// If there is no matching template, nil is returned. func (style *Stylesheet) LookupTemplate(node xml.Node, mode string, context *ExecutionContext) (template *Template) { name := node.Name() if node.NodeType() == xml.XML_DOCUMENT_NODE { name = "/" } found := new(list.List) l := style.ElementMatches[name] if l != nil { for i := l.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } } l = style.ElementMatches["*"] if l != nil { for i := l.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } } l = style.AttrMatches[name] if l != nil { for i := l.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } } l = style.AttrMatches["*"] if l != nil { for i := l.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } } //TODO: review order in which we consult generic matches for i := style.IdKeyMatches.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } for i := style.NodeMatches.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } for i := style.TextMatches.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } for i := style.PIMatches.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } for i := style.CommentMatches.Front(); i != nil; i = i.Next() { c := i.Value.(*CompiledMatch) if c.EvalMatch(node, mode, context) { insertByPriority(found, c) break } } // if there's a match at this import precedence, return // the one with the highest priority f := found.Front() if f != nil { template = f.Value.(*CompiledMatch).Template return } // no match at this import precedence, //consult the imported stylesheets for i := style.Imports.Front(); i != nil; i = i.Next() { s := i.Value.(*Stylesheet) t := s.LookupTemplate(node, mode, context) if t != nil { return t } } return }
// Evaluate an instruction and generate output nodes func (i *XsltInstruction) Apply(node xml.Node, context *ExecutionContext) { //push context if children to apply! switch i.Name { case "apply-templates": scope := i.Node.Attr("select") mode := i.Node.Attr("mode") // #current is a 2.0 keyword if mode != context.Mode && mode != "#current" { context.Mode = mode } // TODO: determine with-params at compile time var params []*Variable for _, cur := range i.Children { switch p := cur.(type) { case *Variable: if IsXsltName(p.Node, "with-param") { p.Apply(node, context) params = append(params, p) } } } // By default, scope is children of current node if scope == "" { children := context.ChildrenOf(node) if i.sorting != nil { i.Sort(children, context) } total := len(children) oldpos, oldtotal := context.XPathContext.GetContextPosition() oldcurr := context.Current for i, cur := range children { context.XPathContext.SetContextPosition(i+1, total) //processNode will update Context.Current whenever a template is invoked context.Style.processNode(cur, context, params) } context.XPathContext.SetContextPosition(oldpos, oldtotal) context.Current = oldcurr return } context.RegisterXPathNamespaces(i.Node) e := xpath.Compile(scope) // TODO: ensure we apply strip-space if required nodes, err := context.EvalXPathAsNodeset(node, e) if err != nil { fmt.Println("apply-templates @select", err) } if i.sorting != nil { i.Sort(nodes, context) } total := len(nodes) oldpos, oldtotal := context.XPathContext.GetContextPosition() oldcurr := context.Current for i, cur := range nodes { context.XPathContext.SetContextPosition(i+1, total) context.Style.processNode(cur, context, params) } context.XPathContext.SetContextPosition(oldpos, oldtotal) context.Current = oldcurr case "number": i.numbering(node, context) case "text": disableEscaping := i.Node.Attr("disable-output-escaping") == "yes" content := i.Node.Content() //don't bother creating a text node for an empty string if content != "" { r := context.Output.CreateTextNode(content) if disableEscaping { r.DisableOutputEscaping() } context.OutputNode.AddChild(r) } case "call-template": name := i.Node.Attr("name") t, ok := context.Style.NamedTemplates[name] if ok && t != nil { // TODO: determine with-params at compile time var params []*Variable for _, cur := range i.Children { switch p := cur.(type) { case *Variable: if IsXsltName(p.Node, "with-param") { p.Apply(node, context) params = append(params, p) } } } t.Apply(node, context, params) } case "element": ename := i.Node.Attr("name") if strings.ContainsRune(ename, '{') { ename = evalAVT(ename, node, context) } r := context.Output.CreateElementNode(ename) ns := i.Node.Attr("namespace") if strings.ContainsRune(ns, '{') { ns = evalAVT(ns, node, context) } if ns != "" { //TODO: search through namespaces in-scope // not just top-level stylesheet mappings prefix, _ := context.Style.NamespaceMapping[ns] r.SetNamespace(prefix, ns) } else { // if no namespace specified, use the default namespace // in scope at this point in the stylesheet defaultNS := context.DefaultNamespace(i.Node) if defaultNS != "" { r.SetNamespace("", defaultNS) } } context.OutputNode.AddChild(r) context.DeclareStylesheetNamespacesIfRoot(r) old := context.OutputNode context.OutputNode = r attsets := i.Node.Attr("use-attribute-sets") if attsets != "" { asets := strings.Fields(attsets) for _, attsetname := range asets { a, _ := context.Style.AttributeSets[attsetname] if a != nil { a.Apply(node, context) } } } for _, c := range i.Children { c.Apply(node, context) } context.OutputNode = old case "comment": val, _ := i.evalChildrenAsText(node, context) r := context.Output.CreateCommentNode(val) context.OutputNode.AddChild(r) case "processing-instruction": name := i.Node.Attr("name") val, _ := i.evalChildrenAsText(node, context) //TODO: it is an error if val contains "?>" r := context.Output.CreatePINode(name, val) context.OutputNode.AddChild(r) case "attribute": aname := i.Node.Attr("name") if strings.ContainsRune(aname, '{') { aname = evalAVT(aname, node, context) } ahref := i.Node.Attr("namespace") if strings.ContainsRune(ahref, '{') { ahref = evalAVT(ahref, node, context) } val, _ := i.evalChildrenAsText(node, context) if ahref == "" { context.OutputNode.SetAttr(aname, val) } else { decl := context.OutputNode.DeclaredNamespaces() dfound := false for _, d := range decl { if ahref == d.Uri { dfound = true break } } if !dfound && ahref != XML_NAMESPACE { //TODO: increment val of generated prefix context.OutputNode.DeclareNamespace("ns_1", ahref) } //if a QName, we ignore the prefix when setting namespace if strings.Contains(aname, ":") { aname = aname[strings.Index(aname, ":")+1:] } context.OutputNode.SetNsAttr(ahref, aname, val) } //context.OutputNode.AddChild(a) case "value-of": e := xpath.Compile(i.Node.Attr("select")) disableEscaping := i.Node.Attr("disable-output-escaping") == "yes" context.RegisterXPathNamespaces(i.Node) content, _ := context.EvalXPathAsString(node, e) //don't bother creating a text node for an empty string if content != "" { if context.UseCDataSection(context.OutputNode) { olddata := context.OutputNode.LastChild() if olddata == nil || olddata.(*xml.CDataNode) == nil { r := context.Output.CreateCDataNode(content) context.OutputNode.AddChild(r) } else { r := context.Output.CreateCDataNode(olddata.Content() + content) context.OutputNode.AddChild(r) olddata.Remove() } } else { r := context.Output.CreateTextNode(content) if disableEscaping { r.DisableOutputEscaping() } context.OutputNode.AddChild(r) } } case "when": case "if": e := xpath.Compile(i.Node.Attr("test")) if context.EvalXPathAsBoolean(node, e) { for _, c := range i.Children { c.Apply(node, context) } } case "attribute-set": othersets := i.Node.Attr("use-attribute-sets") if othersets != "" { asets := strings.Fields(othersets) for _, attsetname := range asets { a := context.Style.LookupAttributeSet(attsetname) if a != nil { a.Apply(node, context) } } } for _, c := range i.Children { c.Apply(node, context) } case "fallback": for _, c := range i.Children { c.Apply(node, context) } case "otherwise": for _, c := range i.Children { c.Apply(node, context) } case "choose": for _, c := range i.Children { inst := c.(*XsltInstruction) if inst.Node.Name() == "when" { xp := xpath.Compile(inst.Node.Attr("test")) if context.EvalXPathAsBoolean(node, xp) { for _, wc := range inst.Children { wc.Apply(node, context) } break } } else { inst.Apply(node, context) } } case "copy": //i.copyToOutput(cur, context, false) switch node.NodeType() { case xml.XML_TEXT_NODE: if context.UseCDataSection(context.OutputNode) { r := context.Output.CreateCDataNode(node.Content()) context.OutputNode.AddChild(r) } else { r := context.Output.CreateTextNode(node.Content()) context.OutputNode.AddChild(r) } case xml.XML_ATTRIBUTE_NODE: aname := node.Name() ahref := node.Namespace() val := node.Content() if ahref == "" { context.OutputNode.SetAttr(aname, val) } else { context.OutputNode.SetNsAttr(ahref, aname, val) } case xml.XML_COMMENT_NODE: r := context.Output.CreateCommentNode(node.Content()) context.OutputNode.AddChild(r) case xml.XML_PI_NODE: name := node.Name() r := context.Output.CreatePINode(name, node.Content()) context.OutputNode.AddChild(r) case xml.XML_ELEMENT_NODE: aname := node.Name() r := context.Output.CreateElementNode(aname) context.OutputNode.AddChild(r) ns := node.Namespace() if ns != "" { //TODO: search through namespaces in-scope prefix, _ := context.Style.NamespaceMapping[ns] r.SetNamespace(prefix, ns) } //copy namespace declarations for _, decl := range node.DeclaredNamespaces() { r.DeclareNamespace(decl.Prefix, decl.Uri) } old := context.OutputNode context.OutputNode = r attsets := i.Node.Attr("use-attribute-sets") if attsets != "" { asets := strings.Fields(attsets) for _, attsetname := range asets { a := context.Style.LookupAttributeSet(attsetname) if a != nil { a.Apply(node, context) } } } for _, c := range i.Children { c.Apply(node, context) } context.OutputNode = old } case "for-each": scope := i.Node.Attr("select") e := xpath.Compile(scope) context.RegisterXPathNamespaces(i.Node) nodes, _ := context.EvalXPathAsNodeset(node, e) if i.sorting != nil { i.Sort(nodes, context) } total := len(nodes) old_curr := context.Current for j, cur := range nodes { context.PushStack() context.XPathContext.SetContextPosition(j+1, total) context.Current = cur for _, c := range i.Children { c.Apply(cur, context) switch v := c.(type) { case *Variable: _ = context.DeclareLocalVariable(v.Name, "", v) } } context.PopStack() } context.Current = old_curr case "copy-of": scope := i.Node.Attr("select") e := xpath.Compile(scope) context.RegisterXPathNamespaces(i.Node) nodes, _ := context.EvalXPathAsNodeset(node, e) total := len(nodes) for j, cur := range nodes { context.XPathContext.SetContextPosition(j+1, total) i.copyToOutput(cur, context, true) } case "message": val, _ := i.evalChildrenAsText(node, context) terminate := i.Node.Attr("terminate") if terminate == "yes" { //TODO: fixup error flow to terminate more gracefully panic(val) } else { fmt.Println(val) } case "apply-imports": fmt.Println("TODO handle xsl:apply-imports instruction") default: hasFallback := false for _, c := range i.Children { switch v := c.(type) { case *XsltInstruction: if v.Name == "fallback" { c.Apply(node, context) hasFallback = true break } } } if !hasFallback { fmt.Println("UNKNOWN instruction ", i.Name) } } }
// Here we iterate through the children; this has been moved to its own function // to facilitate the implementation of xsl:include (where we want the children to // be treated as if they were part of the calling stylesheet) func (style *Stylesheet) parseChildren(root xml.Node, fileuri string) (err error) { //iterate through children for cur := root.FirstChild(); cur != nil; cur = cur.NextSibling() { //skip blank nodes if IsBlank(cur) { continue } //skip comment nodes if cur.NodeType() == xml.XML_COMMENT_NODE { continue } //handle templates if IsXsltName(cur, "template") { style.ParseTemplate(cur) continue } if IsXsltName(cur, "variable") { style.RegisterGlobalVariable(cur) continue } if IsXsltName(cur, "key") { name := cur.Attr("name") use := cur.Attr("use") match := cur.Attr("match") k := &Key{make(map[string]xml.Nodeset), use, match} style.Keys[name] = k continue } //TODO: this is cheating. Also note global params can have their // value overwritten if IsXsltName(cur, "param") { style.RegisterGlobalVariable(cur) continue } if IsXsltName(cur, "attribute-set") { style.RegisterAttributeSet(cur) continue } if IsXsltName(cur, "include") { //check for recursion, multiple includes loc := cur.Attr("href") base := path.Dir(fileuri) loc = path.Join(base, loc) _, already := style.includes[loc] if already { panic("Multiple include detected of " + loc) } style.includes[loc] = true //load the stylesheet doc, e := xml.ReadFile(loc, xml.StrictParseOption) if e != nil { fmt.Println(e) err = e return } //_, _ = ParseStylesheet(doc, loc) //update the including stylesheet e = style.parseChildren(doc.Root(), loc) if e != nil { fmt.Println(e) err = e return } continue } if IsXsltName(cur, "import") { //check for recursion, multiple includes loc := cur.Attr("href") base := path.Dir(fileuri) loc = path.Join(base, loc) _, already := style.includes[loc] if already { panic("Multiple include detected of " + loc) } style.includes[loc] = true //increment import; new style context doc, _ := xmlReadFile(loc) _import, _ := ParseStylesheet(doc, loc) style.Imports.PushFront(_import) continue } if IsXsltName(cur, "output") { cdata := cur.Attr("cdata-section-elements") if cdata != "" { style.CDataElements = strings.Fields(cdata) } style.OutputMethod = cur.Attr("method") omit := cur.Attr("omit-xml-declaration") if omit == "yes" { style.OmitXmlDeclaration = true } indent := cur.Attr("indent") if indent == "yes" { style.IndentOutput = true } standalone := cur.Attr("standalone") if standalone == "yes" { style.Standalone = true } encoding := cur.Attr("encoding") if encoding != "" && encoding != "utf-8" { //TODO: emit a warning if we do not support the encoding // if unsupported, leave blank to output default UTF-8 style.DesiredEncoding = encoding } style.doctypeSystem = cur.Attr("doctype-system") style.doctypePublic = cur.Attr("doctype-public") continue } if IsXsltName(cur, "strip-space") { el := cur.Attr("elements") if el != "" { style.StripSpace = strings.Fields(el) } continue } if IsXsltName(cur, "preserve-space") { el := cur.Attr("elements") if el != "" { style.PreserveSpace = strings.Fields(el) } continue } if IsXsltName(cur, "namespace-alias") { stylens := cur.Attr("stylesheet-prefix") resns := cur.Attr("result-prefix") style.NamespaceAlias[stylens] = resns continue } if IsXsltName(cur, "decimal-format") { fmt.Println("GLOBAL TODO ", cur.Name()) continue } } return }