// As the Search function, but passing a VariableScope that can be used to reolve variable // names or registered function references in the XPath being evaluated. func (xmlNode *XmlNode) SearchWithVariables(data interface{}, v xpath.VariableScope) (result []Node, err error) { switch data := data.(type) { default: err = ERR_UNDEFINED_SEARCH_PARAM case string: if xpathExpr := xpath.Compile(data); xpathExpr != nil { defer xpathExpr.Free() result, err = xmlNode.SearchWithVariables(xpathExpr, v) } else { err = errors.New("cannot compile xpath: " + data) } case []byte: result, err = xmlNode.SearchWithVariables(string(data), v) case *xpath.Expression: xpathCtx := xmlNode.Document.DocXPathCtx() xpathCtx.SetResolver(v) nodePtrs, err := xpathCtx.EvaluateAsNodeset(unsafe.Pointer(xmlNode.Ptr), data) if nodePtrs == nil || err != nil { return nil, err } for _, nodePtr := range nodePtrs { result = append(result, NewNode(nodePtr, xmlNode.Document)) } } return }
func httpGetXpath(link, xpathStr string) (string, error) { buf, err := httpGet(link) if err != nil { return "", err } doc, err := gokogiri.ParseHtml(buf) defer doc.Free() if err != nil { return "", err } if doc.Root() == nil { return "", errElementNotFound } xpath := xpath.Compile(xpathStr) defer xpath.Free() sr, err := doc.Root().Search(xpath) if err != nil { return "", err } if len(sr) > 0 { return sr[0].InnerHtml(), nil } return "", errElementNotFound }
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 }
// Applying a variable node is calculating its value. func (i *Variable) Apply(node xml.Node, context *ExecutionContext) { scope := i.Node.Attr("select") // if @select if scope != "" { e := xpath.Compile(scope) var err error context.RegisterXPathNamespaces(i.Node) i.Value, err = context.EvalXPath(node, e) if err != nil { fmt.Println("Error evaluating variable", i.Name, err) } //fmt.Println("VARIABLE SELECT", i.Name, i.Value) return } if len(i.Children) == 0 { //fmt.Println("VARIABLE NIL", name, i.Value) i.Value = nil return } // if multiple children, return nodeset curOutput := context.OutputNode context.OutputNode = context.Output.CreateElementNode("RVT") context.PushStack() for _, c := range i.Children { c.Apply(node, context) switch v := c.(type) { case *Variable: _ = context.DeclareLocalVariable(v.Name, "", v) } } context.PopStack() i.Value = nil var outNodes xml.Nodeset for cur := context.OutputNode.FirstChild(); cur != nil; cur = cur.NextSibling() { outNodes = append(outNodes, cur) } i.Value = outNodes context.OutputNode = curOutput //fmt.Println("VARIABLE NODES", name, i.Value) }
// Evaluate an XPath and return a result of the appropriate type. // If a non-nil VariableScope is provided, any variables or functions present // in the xpath will be resolved. // // If the result is a nodeset (or the empty nodeset), a nodeset will be returned. // // If the result is a number, a float64 will be returned. // // If the result is a boolean, a bool will be returned. // // In any other cases, the result will be coerced to a string. func (xmlNode *XmlNode) EvalXPath(data interface{}, v xpath.VariableScope) (result interface{}, err error) { switch data := data.(type) { case string: if xpathExpr := xpath.Compile(data); xpathExpr != nil { defer xpathExpr.Free() result, err = xmlNode.EvalXPath(xpathExpr, v) } else { err = errors.New("cannot compile xpath: " + data) } case []byte: result, err = xmlNode.EvalXPath(string(data), v) case *xpath.Expression: xpathCtx := xmlNode.Document.DocXPathCtx() xpathCtx.SetResolver(v) err := xpathCtx.Evaluate(unsafe.Pointer(xmlNode.Ptr), 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 []Node for _, nodePtr := range nodePtrs { output = append(output, NewNode(nodePtr, xmlNode.Document)) } result = output case xpath.XPATH_NUMBER: result, _ = xpathCtx.ResultAsNumber() case xpath.XPATH_BOOLEAN: result, _ = xpathCtx.ResultAsBoolean() default: result, _ = xpathCtx.ResultAsString() } default: err = ERR_UNDEFINED_SEARCH_PARAM } return }
// Evaluate an XPath and coerce the result to a boolean according to the // XPath rules. In the presence of an error, this function will return false // even if the expression cannot actually be evaluated. // // In most cases you are better advised to call EvalXPath; this function is // intended for packages that implement XML standards and that are fully aware // of the consequences of suppressing a compilation error. // // If a non-nil VariableScope is provided, any variables or registered functions present // in the xpath will be resolved. func (xmlNode *XmlNode) EvalXPathAsBoolean(data interface{}, v xpath.VariableScope) (result bool) { switch data := data.(type) { case string: if xpathExpr := xpath.Compile(data); xpathExpr != nil { defer xpathExpr.Free() result = xmlNode.EvalXPathAsBoolean(xpathExpr, v) } else { //err = errors.New("cannot compile xpath: " + data) } case []byte: result = xmlNode.EvalXPathAsBoolean(string(data), v) case *xpath.Expression: xpathCtx := xmlNode.Document.DocXPathCtx() xpathCtx.SetResolver(v) err := xpathCtx.Evaluate(unsafe.Pointer(xmlNode.Ptr), data) if err != nil { return false } result, _ = xpathCtx.ResultAsBoolean() default: //err = ERR_UNDEFINED_SEARCH_PARAM } return }
// 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 }
// 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": for _, c := range i.Children { c.Apply(node, context) } 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) } } } 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) } } }