// The output is not guaranteed to be well-formed XML, so the // serialized string is returned. Consideration is being given // to returning a slice of bytes and encoding information. func (style *Stylesheet) Process(doc *xml.XmlDocument, options StylesheetOptions) (out string, err error) { // lookup output method, doctypes, encoding // create output document with appropriate values output := xml.CreateEmptyDocument(doc.InputEncoding(), doc.OutputEncoding()) // init context node/document context := &ExecutionContext{Output: output.Me, OutputNode: output, Style: style, Source: doc} context.Current = doc context.XPathContext = doc.DocXPathCtx() // when evaluating keys/global vars position is always 1 context.XPathContext.SetContextPosition(1, 1) start := doc style.populateKeys(start, context) // eval global params // eval global variables for _, val := range style.Variables { val.Apply(doc, context) } // set xpath context // process nodes style.processNode(start, context, nil) out, err = style.constructOutput(output, options) // reset anything required for re-use return }
// ParseStylesheet compiles the stylesheet's XML representation // and returns a Stylesheet instance. // // The fileuri argument is used to resolve relative paths for xsl:import and xsl:include // instructions and should generally be the filename of the stylesheet. If you pass // an empty string, the working directory will be used for path resolution. func ParseStylesheet(doc *xml.XmlDocument, fileuri string) (style *Stylesheet, err error) { style = &Stylesheet{Doc: doc, NamespaceMapping: make(map[string]string), NamespaceAlias: make(map[string]string), ElementMatches: make(map[string]*list.List), AttrMatches: make(map[string]*list.List), PIMatches: list.New(), CommentMatches: list.New(), IdKeyMatches: list.New(), NodeMatches: list.New(), TextMatches: list.New(), Imports: list.New(), NamedTemplates: make(map[string]*Template), AttributeSets: make(map[string]CompiledStep), includes: make(map[string]bool), Keys: make(map[string]*Key), Functions: make(map[string]xpath.XPathFunction), Variables: make(map[string]*Variable)} // register the built-in XSLT functions style.RegisterXsltFunctions() //XsltParseStylesheetProcess cur := xml.Node(doc.Root()) // get all the namespace mappings for _, ns := range cur.DeclaredNamespaces() { style.NamespaceMapping[ns.Uri] = ns.Prefix } //get xsl:version, should be 1.0 or 2.0 version := cur.Attr("version") if version != "1.0" { log.Println("VERSION 1.0 expected") } //record excluded prefixes excl := cur.Attr("exclude-result-prefixes") if excl != "" { style.ExcludePrefixes = strings.Fields(excl) } //record extension prefixes ext := cur.Attr("extension-element-prefixes") if ext != "" { style.ExtensionPrefixes = strings.Fields(ext) } //if the root is an LRE, this is an simplified stylesheet if !IsXsltName(cur, "stylesheet") && !IsXsltName(cur, "transform") { template := &Template{Match: "/", Priority: 0} template.CompileContent(doc) style.compilePattern(template, "") return } //optionally optimize by removing blank nodes, combining adjacent text nodes, etc err = style.parseChildren(cur, fileuri) //xsl:import (must be first) //flag non-empty text nodes, non XSL-namespaced nodes // actually registered extension namspaces are good! //warn unknown XSLT element (forwards-compatible mode) return }
// actually produce (and possibly write) the final output func (style *Stylesheet) constructOutput(output *xml.XmlDocument, options StylesheetOptions) (out string, err error) { //if not explicitly set, spec requires us to check for html outputType := style.OutputMethod if outputType == "" { outputType = "xml" root := output.Root() if root != nil && root.Name() == "html" && root.Namespace() == "" { outputType = "html" } } // construct DTD declaration depending on xsl:output settings docType := "" if style.doctypeSystem != "" { docType = "<!DOCTYPE " docType = docType + output.Root().Name() if style.doctypePublic != "" { docType = docType + fmt.Sprintf(" PUBLIC \"%s\"", style.doctypePublic) } else { docType = docType + " SYSTEM" } docType = docType + fmt.Sprintf(" \"%s\"", style.doctypeSystem) docType = docType + ">\n" } // create the XML declaration depending on xsl:output settings decl := "" if outputType == "xml" { if !style.OmitXmlDeclaration { decl = style.constructXmlDeclaration() } format := xml.XML_SAVE_NO_DECL | xml.XML_SAVE_AS_XML if options.IndentOutput || style.IndentOutput { format = format | xml.XML_SAVE_FORMAT } // we get slightly incorrect output if we call out.SerializeWithFormat directly // this seems to be a libxml bug; we work around it the same way libxslt does //TODO: honor desired encoding // this involves decisions about supported encodings, strings vs byte slices // we can sidestep a little if we enable option to write directly to file for cur := output.FirstChild(); cur != nil; cur = cur.NextSibling() { b, size := cur.SerializeWithFormat(format, nil, nil) if b != nil { out = out + string(b[:size]) } } if out != "" { out = decl + docType + out + "\n" } } if outputType == "html" { out = docType b, size := output.ToHtml(nil, nil) out = out + string(b[:size]) } if outputType == "text" { format := xml.XML_SAVE_NO_DECL for cur := output.FirstChild(); cur != nil; cur = cur.NextSibling() { b, size := cur.SerializeWithFormat(format, nil, nil) if b != nil { out = out + string(b[:size]) } } } return }