// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.
// Package xml implements a simple XML 1.0 parser that// understands XML name spaces.
package xml// References:// Annotated XML spec: https://www.xml.com/axml/testaxml.htm// XML name spaces: https://www.w3.org/TR/REC-xml-names/import ()// A SyntaxError represents a syntax error in the XML input stream.typeSyntaxErrorstruct { Msg string Line int}func ( *SyntaxError) () string {return"XML syntax error on line " + strconv.Itoa(.Line) + ": " + .Msg}// A Name represents an XML name (Local) annotated// with a name space identifier (Space).// In tokens returned by [Decoder.Token], the Space identifier// is given as a canonical URL, not the short prefix used// in the document being parsed.typeNamestruct { Space, Local string}// An Attr represents an attribute in an XML element (Name=Value).typeAttrstruct { Name Name Value string}// A Token is an interface holding one of the token types:// [StartElement], [EndElement], [CharData], [Comment], [ProcInst], or [Directive].typeTokenany// A StartElement represents an XML start element.typeStartElementstruct { Name Name Attr []Attr}// Copy creates a new copy of StartElement.func ( StartElement) () StartElement { := make([]Attr, len(.Attr))copy(, .Attr) .Attr = return}// End returns the corresponding XML end element.func ( StartElement) () EndElement {returnEndElement{.Name}}// An EndElement represents an XML end element.typeEndElementstruct { Name Name}// A CharData represents XML character data (raw text),// in which XML escape sequences have been replaced by// the characters they represent.typeCharData []byte// Copy creates a new copy of CharData.func ( CharData) () CharData { returnCharData(bytes.Clone()) }// A Comment represents an XML comment of the form <!--comment-->.// The bytes do not include the <!-- and --> comment markers.typeComment []byte// Copy creates a new copy of Comment.func ( Comment) () Comment { returnComment(bytes.Clone()) }// A ProcInst represents an XML processing instruction of the form <?target inst?>typeProcInststruct { Target string Inst []byte}// Copy creates a new copy of ProcInst.func ( ProcInst) () ProcInst { .Inst = bytes.Clone(.Inst)return}// A Directive represents an XML directive of the form <!text>.// The bytes do not include the <! and > markers.typeDirective []byte// Copy creates a new copy of Directive.func ( Directive) () Directive { returnDirective(bytes.Clone()) }// CopyToken returns a copy of a Token.func ( Token) Token {switch v := .(type) {caseCharData:return .Copy()caseComment:return .Copy()caseDirective:return .Copy()caseProcInst:return .Copy()caseStartElement:return .Copy() }return}// A TokenReader is anything that can decode a stream of XML tokens, including a// [Decoder].//// When Token encounters an error or end-of-file condition after successfully// reading a token, it returns the token. It may return the (non-nil) error from// the same call or return the error (and a nil token) from a subsequent call.// An instance of this general case is that a TokenReader returning a non-nil// token at the end of the token stream may return either io.EOF or a nil error.// The next Read should return nil, [io.EOF].//// Implementations of Token are discouraged from returning a nil token with a// nil error. Callers should treat a return of nil, nil as indicating that// nothing happened; in particular it does not indicate EOF.typeTokenReaderinterface {Token() (Token, error)}// A Decoder represents an XML parser reading a particular input stream.// The parser assumes that its input is encoded in UTF-8.typeDecoderstruct {// Strict defaults to true, enforcing the requirements // of the XML specification. // If set to false, the parser allows input containing common // mistakes: // * If an element is missing an end tag, the parser invents // end tags as necessary to keep the return values from Token // properly balanced. // * In attribute values and character data, unknown or malformed // character entities (sequences beginning with &) are left alone. // // Setting: // // d.Strict = false // d.AutoClose = xml.HTMLAutoClose // d.Entity = xml.HTMLEntity // // creates a parser that can handle typical HTML. // // Strict mode does not enforce the requirements of the XML name spaces TR. // In particular it does not reject name space tags using undefined prefixes. // Such tags are recorded with the unknown prefix as the name space URL. Strict bool// When Strict == false, AutoClose indicates a set of elements to // consider closed immediately after they are opened, regardless // of whether an end element is present. AutoClose []string// Entity can be used to map non-standard entity names to string replacements. // The parser behaves as if these standard mappings are present in the map, // regardless of the actual map content: // // "lt": "<", // "gt": ">", // "amp": "&", // "apos": "'", // "quot": `"`, Entity map[string]string// CharsetReader, if non-nil, defines a function to generate // charset-conversion readers, converting from the provided // non-UTF-8 charset into UTF-8. If CharsetReader is nil or // returns an error, parsing stops with an error. One of the // CharsetReader's result values must be non-nil. CharsetReader func(charset string, input io.Reader) (io.Reader, error)// DefaultSpace sets the default name space used for unadorned tags, // as if the entire XML stream were wrapped in an element containing // the attribute xmlns="DefaultSpace". DefaultSpace string r io.ByteReader t TokenReader buf bytes.Buffer saved *bytes.Buffer stk *stack free *stack needClose bool toClose Name nextToken Token nextByte int ns map[string]string err error line int linestart int64 offset int64 unmarshalDepth int}// NewDecoder creates a new XML parser reading from r.// If r does not implement [io.ByteReader], NewDecoder will// do its own buffering.func ( io.Reader) *Decoder { := &Decoder{ns: make(map[string]string),nextByte: -1,line: 1,Strict: true, } .switchToReader()return}// NewTokenDecoder creates a new XML parser using an underlying token stream.func ( TokenReader) *Decoder {// Is it already a Decoder?if , := .(*Decoder); {return } := &Decoder{ns: make(map[string]string),t: ,nextByte: -1,line: 1,Strict: true, }return}// Token returns the next XML token in the input stream.// At the end of the input stream, Token returns nil, [io.EOF].//// Slices of bytes in the returned token data refer to the// parser's internal buffer and remain valid only until the next// call to Token. To acquire a copy of the bytes, call [CopyToken]// or the token's Copy method.//// Token expands self-closing elements such as <br>// into separate start and end elements returned by successive calls.//// Token guarantees that the [StartElement] and [EndElement]// tokens it returns are properly nested and matched:// if Token encounters an unexpected end element// or EOF before all expected end elements,// it will return an error.//// If [Decoder.CharsetReader] is called and returns an error,// the error is wrapped and returned.//// Token implements XML name spaces as described by// https://www.w3.org/TR/REC-xml-names/. Each of the// [Name] structures contained in the Token has the Space// set to the URL identifying its name space when known.// If Token encounters an unrecognized name space prefix,// it uses the prefix as the Space rather than report an error.func ( *Decoder) () (Token, error) {varTokenvarerrorif .stk != nil && .stk.kind == stkEOF {returnnil, io.EOF }if .nextToken != nil { = .nextToken .nextToken = nil } else {if , = .rawToken(); == nil && != nil {if == io.EOF && .stk != nil && .stk.kind != stkEOF { = .syntaxError("unexpected EOF") }returnnil, }// We still have a token to process, so clear any // errors (e.g. EOF) and proceed. = nil }if !.Strict {if , := .autoClose(); { .nextToken = = } }switch t1 := .(type) {caseStartElement:// In XML name spaces, the translations listed in the // attributes apply to the element name and // to the other attribute names, so process // the translations first.for , := range .Attr {if .Name.Space == xmlnsPrefix { , := .ns[.Name.Local] .pushNs(.Name.Local, , ) .ns[.Name.Local] = .Value }if .Name.Space == "" && .Name.Local == xmlnsPrefix {// Default space for untagged names , := .ns[""] .pushNs("", , ) .ns[""] = .Value } } .pushElement(.Name) .translate(&.Name, true)for := range .Attr { .translate(&.Attr[].Name, false) } = caseEndElement:if !.popElement(&) {returnnil, .err } = }return , }const ( xmlURL = "http://www.w3.org/XML/1998/namespace" xmlnsPrefix = "xmlns" xmlPrefix = "xml")// Apply name space translation to name n.// The default name space (for Space=="")// applies only to element names, not to attribute names.func ( *Decoder) ( *Name, bool) {switch {case .Space == xmlnsPrefix:returncase .Space == "" && !:returncase .Space == xmlPrefix: .Space = xmlURLcase .Space == "" && .Local == xmlnsPrefix:return }if , := .ns[.Space]; { .Space = } elseif .Space == "" { .Space = .DefaultSpace }}func ( *Decoder) ( io.Reader) {// Get efficient byte at a time reader. // Assume that if reader has its own // ReadByte, it's efficient enough. // Otherwise, use bufio.if , := .(io.ByteReader); { .r = } else { .r = bufio.NewReader() }}// Parsing state - stack holds old name space translations// and the current set of open elements. The translations to pop when// ending a given tag are *below* it on the stack, which is// more work but forced on us by XML.type stack struct { next *stack kind int name Name ok bool}const ( stkStart = iota stkNs stkEOF)func ( *Decoder) ( int) *stack { := .freeif != nil { .free = .next } else { = new(stack) } .next = .stk .kind = .stk = return}func ( *Decoder) () *stack { := .stkif != nil { .stk = .next .next = .free .free = }return}// Record that after the current element is finished// (that element is already pushed on the stack)// Token should return EOF until popEOF is called.func ( *Decoder) () {// Walk down stack to find Start. // It might not be the top, because there might be stkNs // entries above it. := .stkfor .kind != stkStart { = .next }// The stkNs entries below a start are associated with that // element too; skip over them.for .next != nil && .next.kind == stkNs { = .next } := .freeif != nil { .free = .next } else { = new(stack) } .kind = stkEOF .next = .next .next = }// Undo a pushEOF.// The element must have been finished, so the EOF should be at the top of the stack.func ( *Decoder) () bool {if .stk == nil || .stk.kind != stkEOF {returnfalse } .pop()returntrue}// Record that we are starting an element with the given name.func ( *Decoder) ( Name) { := .push(stkStart) .name = }// Record that we are changing the value of ns[local].// The old value is url, ok.func ( *Decoder) ( string, string, bool) { := .push(stkNs) .name.Local = .name.Space = .ok = }// Creates a SyntaxError with the current line number.func ( *Decoder) ( string) error {return &SyntaxError{Msg: , Line: .line}}// Record that we are ending an element with the given name.// The name must match the record at the top of the stack,// which must be a pushElement record.// After popping the element, apply any undo records from// the stack to restore the name translations that existed// before we saw this element.func ( *Decoder) ( *EndElement) bool { := .pop() := .Nameswitch {case == nil || .kind != stkStart: .err = .syntaxError("unexpected end element </" + .Local + ">")returnfalsecase .name.Local != .Local:if !.Strict { .needClose = true .toClose = .Name .Name = .namereturntrue } .err = .syntaxError("element <" + .name.Local + "> closed by </" + .Local + ">")returnfalsecase .name.Space != .Space: := .Spaceif .Space == "" { = `""` } .err = .syntaxError("element <" + .name.Local + "> in space " + .name.Space +" closed by </" + .Local + "> in space " + )returnfalse } .translate(&.Name, true)// Pop stack until a Start or EOF is on the top, undoing the // translations that were associated with the element we just closed.for .stk != nil && .stk.kind != stkStart && .stk.kind != stkEOF { := .pop()if .ok { .ns[.name.Local] = .name.Space } else {delete(.ns, .name.Local) } }returntrue}// If the top element on the stack is autoclosing and// t is not the end tag, invent the end tag.func ( *Decoder) ( Token) (Token, bool) {if .stk == nil || .stk.kind != stkStart {returnnil, false }for , := range .AutoClose {ifstrings.EqualFold(, .stk.name.Local) {// This one should be auto closed if t doesn't close it. , := .(EndElement)if ! || !strings.EqualFold(.Name.Local, .stk.name.Local) {returnEndElement{.stk.name}, true }break } }returnnil, false}var errRawToken = errors.New("xml: cannot use RawToken from UnmarshalXML method")// RawToken is like [Decoder.Token] but does not verify that// start and end elements match and does not translate// name space prefixes to their corresponding URLs.func ( *Decoder) () (Token, error) {if .unmarshalDepth > 0 {returnnil, errRawToken }return .rawToken()}func ( *Decoder) () (Token, error) {if .t != nil {return .t.Token() }if .err != nil {returnnil, .err }if .needClose {// The last element we read was self-closing and // we returned just the StartElement half. // Return the EndElement half now. .needClose = falsereturnEndElement{.toClose}, nil } , := .getc()if ! {returnnil, .err }if != '<' {// Text section. .ungetc() := .text(-1, false)if == nil {returnnil, .err }returnCharData(), nil }if , = .mustgetc(); ! {returnnil, .err }switch {case'/':// </: End elementvarNameif , = .nsname(); ! {if .err == nil { .err = .syntaxError("expected element name after </") }returnnil, .err } .space()if , = .mustgetc(); ! {returnnil, .err }if != '>' { .err = .syntaxError("invalid characters between </" + .Local + " and >")returnnil, .err }returnEndElement{}, nilcase'?':// <?: Processing instruction.varstringif , = .name(); ! {if .err == nil { .err = .syntaxError("expected target name after <?") }returnnil, .err } .space() .buf.Reset()varbytefor {if , = .mustgetc(); ! {returnnil, .err } .buf.WriteByte()if == '?' && == '>' {break } = } := .buf.Bytes() = [0 : len()-2] // chop ?>if == "xml" { := string() := procInst("version", )if != "" && != "1.0" { .err = fmt.Errorf("xml: unsupported version %q; only version 1.0 is supported", )returnnil, .err } := procInst("encoding", )if != "" && != "utf-8" && != "UTF-8" && !strings.EqualFold(, "utf-8") {if .CharsetReader == nil { .err = fmt.Errorf("xml: encoding %q declared but Decoder.CharsetReader is nil", )returnnil, .err } , := .CharsetReader(, .r.(io.Reader))if != nil { .err = fmt.Errorf("xml: opening charset %q: %w", , )returnnil, .err }if == nil {panic("CharsetReader returned a nil Reader for charset " + ) } .switchToReader() } }returnProcInst{, }, nilcase'!':// <!: Maybe comment, maybe CDATA.if , = .mustgetc(); ! {returnnil, .err }switch {case'-': // <!-// Probably <!-- for a comment.if , = .mustgetc(); ! {returnnil, .err }if != '-' { .err = .syntaxError("invalid sequence <!- not part of <!--")returnnil, .err }// Look for terminator. .buf.Reset()var , bytefor {if , = .mustgetc(); ! {returnnil, .err } .buf.WriteByte()if == '-' && == '-' {if != '>' { .err = .syntaxError(`invalid sequence "--" not allowed in comments`)returnnil, .err }break } , = , } := .buf.Bytes() = [0 : len()-3] // chop -->returnComment(), nilcase'[': // <![// Probably <![CDATA[.for := 0; < 6; ++ {if , = .mustgetc(); ! {returnnil, .err }if != "CDATA["[] { .err = .syntaxError("invalid <![ sequence")returnnil, .err } }// Have <![CDATA[. Read text until ]]>. := .text(-1, true)if == nil {returnnil, .err }returnCharData(), nil }// Probably a directive: <!DOCTYPE ...>, <!ENTITY ...>, etc. // We don't care, but accumulate for caller. Quoted angle // brackets do not count for nesting. .buf.Reset() .buf.WriteByte() := uint8(0) := 0for {if , = .mustgetc(); ! {returnnil, .err }if == 0 && == '>' && == 0 {break } : .buf.WriteByte()switch {case == : = 0case != 0:// in quotes, no special actioncase == '\'' || == '"': = case == '>' && == 0: --case == '<' && == 0:// Look for <!-- to begin comment. := "!--"for := 0; < len(); ++ {if , = .mustgetc(); ! {returnnil, .err }if != [] {for := 0; < ; ++ { .buf.WriteByte([]) } ++goto } }// Remove < that was written above. .buf.Truncate(.buf.Len() - 1)// Look for terminator.var , bytefor {if , = .mustgetc(); ! {returnnil, .err }if == '-' && == '-' && == '>' {break } , = , }// Replace the comment with a space in the returned Directive // body, so that markup parts that were separated by the comment // (like a "<" and a "!") don't get joined when re-encoding the // Directive, taking new semantic meaning. .buf.WriteByte(' ') } }returnDirective(.buf.Bytes()), nil }// Must be an open element like <a href="foo"> .ungetc()var (Namebool []Attr )if , = .nsname(); ! {if .err == nil { .err = .syntaxError("expected element name after <") }returnnil, .err } = []Attr{}for { .space()if , = .mustgetc(); ! {returnnil, .err }if == '/' { = trueif , = .mustgetc(); ! {returnnil, .err }if != '>' { .err = .syntaxError("expected /> in element")returnnil, .err }break }if == '>' {break } .ungetc() := Attr{}if .Name, = .nsname(); ! {if .err == nil { .err = .syntaxError("expected attribute name in element") }returnnil, .err } .space()if , = .mustgetc(); ! {returnnil, .err }if != '=' {if .Strict { .err = .syntaxError("attribute name without = in element")returnnil, .err } .ungetc() .Value = .Name.Local } else { .space() := .attrval()if == nil {returnnil, .err } .Value = string() } = append(, ) }if { .needClose = true .toClose = }returnStartElement{, }, nil}func ( *Decoder) () []byte { , := .mustgetc()if ! {returnnil }// Handle quoted attribute valuesif == '"' || == '\'' {return .text(int(), false) }// Handle unquoted attribute values for strict parsersif .Strict { .err = .syntaxError("unquoted or missing attribute value in element")returnnil }// Handle unquoted attribute values for unstrict parsers .ungetc() .buf.Reset()for { , = .mustgetc()if ! {returnnil }// https://www.w3.org/TR/REC-html40/intro/sgmltut.html#h-3.2.2if'a' <= && <= 'z' || 'A' <= && <= 'Z' ||'0' <= && <= '9' || == '_' || == ':' || == '-' { .buf.WriteByte() } else { .ungetc()break } }return .buf.Bytes()}// Skip spaces if anyfunc ( *Decoder) () {for { , := .getc()if ! {return }switch {case' ', '\r', '\n', '\t':default: .ungetc()return } }}// Read a single byte.// If there is no byte to read, return ok==false// and leave the error in d.err.// Maintain line number.func ( *Decoder) () ( byte, bool) {if .err != nil {return0, false }if .nextByte >= 0 { = byte(.nextByte) .nextByte = -1 } else { , .err = .r.ReadByte()if .err != nil {return0, false }if .saved != nil { .saved.WriteByte() } }if == '\n' { .line++ .linestart = .offset + 1 } .offset++return , true}// InputOffset returns the input stream byte offset of the current decoder position.// The offset gives the location of the end of the most recently returned token// and the beginning of the next token.func ( *Decoder) () int64 {return .offset}// InputPos returns the line of the current decoder position and the 1 based// input position of the line. The position gives the location of the end of the// most recently returned token.func ( *Decoder) () (, int) {return .line, int(.offset-.linestart) + 1}// Return saved offset.// If we did ungetc (nextByte >= 0), have to back up one.func ( *Decoder) () int { := .saved.Len()if .nextByte >= 0 { -- }return}// Must read a single byte.// If there is no byte to read,// set d.err to SyntaxError("unexpected EOF")// and return ok==falsefunc ( *Decoder) () ( byte, bool) {if , = .getc(); ! {if .err == io.EOF { .err = .syntaxError("unexpected EOF") } }return}// Unread a single byte.func ( *Decoder) ( byte) {if == '\n' { .line-- } .nextByte = int() .offset--}var entity = map[string]rune{"lt": '<',"gt": '>',"amp": '&',"apos": '\'',"quot": '"',}// Read plain text section (XML calls it character data).// If quote >= 0, we are in a quoted string and need to find the matching quote.// If cdata == true, we are in a <![CDATA[ section and need to find ]]>.// On failure return nil and leave the error in d.err.func ( *Decoder) ( int, bool) []byte {var , bytevarint .buf.Reset():for { , := .getc()if ! {if {if .err == io.EOF { .err = .syntaxError("unexpected EOF in CDATA section") }returnnil }break }// <![CDATA[ section ends with ]]>. // It is an error for ]]> to appear in ordinary text.if == ']' && == ']' && == '>' {if { = 2break } .err = .syntaxError("unescaped ]]> not in CDATA section")returnnil }// Stop reading text if we see a <.if == '<' && ! {if >= 0 { .err = .syntaxError("unescaped < inside quoted string")returnnil } .ungetc('<')break }if >= 0 && == byte() {break }if == '&' && ! {// Read escaped character expression up to semicolon. // XML in all its glory allows a document to define and use // its own character names with <!ENTITY ...> directives. // Parsers are required to recognize lt, gt, amp, apos, and quot // even if they have not been declared. := .buf.Len() .buf.WriteByte('&')varboolvarstringvarboolif , = .mustgetc(); ! {returnnil }if == '#' { .buf.WriteByte()if , = .mustgetc(); ! {returnnil } := 10if == 'x' { = 16 .buf.WriteByte()if , = .mustgetc(); ! {returnnil } } := .buf.Len()for'0' <= && <= '9' || == 16 && 'a' <= && <= 'f' || == 16 && 'A' <= && <= 'F' { .buf.WriteByte()if , = .mustgetc(); ! {returnnil } }if != ';' { .ungetc() } else { := string(.buf.Bytes()[:]) .buf.WriteByte(';') , := strconv.ParseUint(, , 64)if == nil && <= unicode.MaxRune { = string(rune()) = true } } } else { .ungetc()if !.readName() {if .err != nil {returnnil } }if , = .mustgetc(); ! {returnnil }if != ';' { .ungetc() } else { := .buf.Bytes()[+1:] .buf.WriteByte(';')ifisName() { := string()if , := entity[]; { = string() = true } elseif .Entity != nil { , = .Entity[] } } } }if { .buf.Truncate() .buf.WriteString() , = 0, 0continue }if !.Strict { , = 0, 0continue } := string(.buf.Bytes()[:])if [len()-1] != ';' { += " (no semicolon)" } .err = .syntaxError("invalid character entity " + )returnnil }// We must rewrite unescaped \r and \r\n into \n.if == '\r' { .buf.WriteByte('\n') } elseif == '\r' && == '\n' {// Skip \r\n--we already wrote \n. } else { .buf.WriteByte() } , = , } := .buf.Bytes() = [0 : len()-]// Inspect each rune for being a disallowed character. := forlen() > 0 { , := utf8.DecodeRune()if == utf8.RuneError && == 1 { .err = .syntaxError("invalid UTF-8")returnnil } = [:]if !isInCharacterRange() { .err = .syntaxError(fmt.Sprintf("illegal character code %U", ))returnnil } }return}// Decide whether the given rune is in the XML Character Range, per// the Char production of https://www.xml.com/axml/testaxml.htm,// Section 2.2 Characters.func isInCharacterRange( rune) ( bool) {return == 0x09 || == 0x0A || == 0x0D || >= 0x20 && <= 0xD7FF || >= 0xE000 && <= 0xFFFD || >= 0x10000 && <= 0x10FFFF}// Get name space name: name with a : stuck in the middle.// The part before the : is the name space identifier.func ( *Decoder) () ( Name, bool) { , := .name()if ! {return }ifstrings.Count(, ":") > 1 {return , false } elseif , , := strings.Cut(, ":"); ! || == "" || == "" { .Local = } else { .Space = .Local = }return , true}// Get name: /first(first|second)*/// Do not set d.err if the name is missing (unless unexpected EOF is received):// let the caller provide better context.func ( *Decoder) () ( string, bool) { .buf.Reset()if !.readName() {return"", false }// Now we check the characters. := .buf.Bytes()if !isName() { .err = .syntaxError("invalid XML name: " + string())return"", false }returnstring(), true}// Read a name and append its bytes to d.buf.// The name is delimited by any single-byte character not valid in names.// All multi-byte characters are accepted; the caller must check their validity.func ( *Decoder) () ( bool) {varbyteif , = .mustgetc(); ! {return }if < utf8.RuneSelf && !isNameByte() { .ungetc()returnfalse } .buf.WriteByte()for {if , = .mustgetc(); ! {return }if < utf8.RuneSelf && !isNameByte() { .ungetc()break } .buf.WriteByte() }returntrue}func isNameByte( byte) bool {return'A' <= && <= 'Z' ||'a' <= && <= 'z' ||'0' <= && <= '9' || == '_' || == ':' || == '.' || == '-'}func isName( []byte) bool {iflen() == 0 {returnfalse } , := utf8.DecodeRune()if == utf8.RuneError && == 1 {returnfalse }if !unicode.Is(first, ) {returnfalse }for < len() { = [:] , = utf8.DecodeRune()if == utf8.RuneError && == 1 {returnfalse }if !unicode.Is(first, ) && !unicode.Is(second, ) {returnfalse } }returntrue}func isNameString( string) bool {iflen() == 0 {returnfalse } , := utf8.DecodeRuneInString()if == utf8.RuneError && == 1 {returnfalse }if !unicode.Is(first, ) {returnfalse }for < len() { = [:] , = utf8.DecodeRuneInString()if == utf8.RuneError && == 1 {returnfalse }if !unicode.Is(first, ) && !unicode.Is(second, ) {returnfalse } }returntrue}// These tables were generated by cut and paste from Appendix B of// the XML spec at https://www.xml.com/axml/testaxml.htm// and then reformatting. First corresponds to (Letter | '_' | ':')// and second corresponds to NameChar.var first = &unicode.RangeTable{R16: []unicode.Range16{ {0x003A, 0x003A, 1}, {0x0041, 0x005A, 1}, {0x005F, 0x005F, 1}, {0x0061, 0x007A, 1}, {0x00C0, 0x00D6, 1}, {0x00D8, 0x00F6, 1}, {0x00F8, 0x00FF, 1}, {0x0100, 0x0131, 1}, {0x0134, 0x013E, 1}, {0x0141, 0x0148, 1}, {0x014A, 0x017E, 1}, {0x0180, 0x01C3, 1}, {0x01CD, 0x01F0, 1}, {0x01F4, 0x01F5, 1}, {0x01FA, 0x0217, 1}, {0x0250, 0x02A8, 1}, {0x02BB, 0x02C1, 1}, {0x0386, 0x0386, 1}, {0x0388, 0x038A, 1}, {0x038C, 0x038C, 1}, {0x038E, 0x03A1, 1}, {0x03A3, 0x03CE, 1}, {0x03D0, 0x03D6, 1}, {0x03DA, 0x03E0, 2}, {0x03E2, 0x03F3, 1}, {0x0401, 0x040C, 1}, {0x040E, 0x044F, 1}, {0x0451, 0x045C, 1}, {0x045E, 0x0481, 1}, {0x0490, 0x04C4, 1}, {0x04C7, 0x04C8, 1}, {0x04CB, 0x04CC, 1}, {0x04D0, 0x04EB, 1}, {0x04EE, 0x04F5, 1}, {0x04F8, 0x04F9, 1}, {0x0531, 0x0556, 1}, {0x0559, 0x0559, 1}, {0x0561, 0x0586, 1}, {0x05D0, 0x05EA, 1}, {0x05F0, 0x05F2, 1}, {0x0621, 0x063A, 1}, {0x0641, 0x064A, 1}, {0x0671, 0x06B7, 1}, {0x06BA, 0x06BE, 1}, {0x06C0, 0x06CE, 1}, {0x06D0, 0x06D3, 1}, {0x06D5, 0x06D5, 1}, {0x06E5, 0x06E6, 1}, {0x0905, 0x0939, 1}, {0x093D, 0x093D, 1}, {0x0958, 0x0961, 1}, {0x0985, 0x098C, 1}, {0x098F, 0x0990, 1}, {0x0993, 0x09A8, 1}, {0x09AA, 0x09B0, 1}, {0x09B2, 0x09B2, 1}, {0x09B6, 0x09B9, 1}, {0x09DC, 0x09DD, 1}, {0x09DF, 0x09E1, 1}, {0x09F0, 0x09F1, 1}, {0x0A05, 0x0A0A, 1}, {0x0A0F, 0x0A10, 1}, {0x0A13, 0x0A28, 1}, {0x0A2A, 0x0A30, 1}, {0x0A32, 0x0A33, 1}, {0x0A35, 0x0A36, 1}, {0x0A38, 0x0A39, 1}, {0x0A59, 0x0A5C, 1}, {0x0A5E, 0x0A5E, 1}, {0x0A72, 0x0A74, 1}, {0x0A85, 0x0A8B, 1}, {0x0A8D, 0x0A8D, 1}, {0x0A8F, 0x0A91, 1}, {0x0A93, 0x0AA8, 1}, {0x0AAA, 0x0AB0, 1}, {0x0AB2, 0x0AB3, 1}, {0x0AB5, 0x0AB9, 1}, {0x0ABD, 0x0AE0, 0x23}, {0x0B05, 0x0B0C, 1}, {0x0B0F, 0x0B10, 1}, {0x0B13, 0x0B28, 1}, {0x0B2A, 0x0B30, 1}, {0x0B32, 0x0B33, 1}, {0x0B36, 0x0B39, 1}, {0x0B3D, 0x0B3D, 1}, {0x0B5C, 0x0B5D, 1}, {0x0B5F, 0x0B61, 1}, {0x0B85, 0x0B8A, 1}, {0x0B8E, 0x0B90, 1}, {0x0B92, 0x0B95, 1}, {0x0B99, 0x0B9A, 1}, {0x0B9C, 0x0B9C, 1}, {0x0B9E, 0x0B9F, 1}, {0x0BA3, 0x0BA4, 1}, {0x0BA8, 0x0BAA, 1}, {0x0BAE, 0x0BB5, 1}, {0x0BB7, 0x0BB9, 1}, {0x0C05, 0x0C0C, 1}, {0x0C0E, 0x0C10, 1}, {0x0C12, 0x0C28, 1}, {0x0C2A, 0x0C33, 1}, {0x0C35, 0x0C39, 1}, {0x0C60, 0x0C61, 1}, {0x0C85, 0x0C8C, 1}, {0x0C8E, 0x0C90, 1}, {0x0C92, 0x0CA8, 1}, {0x0CAA, 0x0CB3, 1}, {0x0CB5, 0x0CB9, 1}, {0x0CDE, 0x0CDE, 1}, {0x0CE0, 0x0CE1, 1}, {0x0D05, 0x0D0C, 1}, {0x0D0E, 0x0D10, 1}, {0x0D12, 0x0D28, 1}, {0x0D2A, 0x0D39, 1}, {0x0D60, 0x0D61, 1}, {0x0E01, 0x0E2E, 1}, {0x0E30, 0x0E30, 1}, {0x0E32, 0x0E33, 1}, {0x0E40, 0x0E45, 1}, {0x0E81, 0x0E82, 1}, {0x0E84, 0x0E84, 1}, {0x0E87, 0x0E88, 1}, {0x0E8A, 0x0E8D, 3}, {0x0E94, 0x0E97, 1}, {0x0E99, 0x0E9F, 1}, {0x0EA1, 0x0EA3, 1}, {0x0EA5, 0x0EA7, 2}, {0x0EAA, 0x0EAB, 1}, {0x0EAD, 0x0EAE, 1}, {0x0EB0, 0x0EB0, 1}, {0x0EB2, 0x0EB3, 1}, {0x0EBD, 0x0EBD, 1}, {0x0EC0, 0x0EC4, 1}, {0x0F40, 0x0F47, 1}, {0x0F49, 0x0F69, 1}, {0x10A0, 0x10C5, 1}, {0x10D0, 0x10F6, 1}, {0x1100, 0x1100, 1}, {0x1102, 0x1103, 1}, {0x1105, 0x1107, 1}, {0x1109, 0x1109, 1}, {0x110B, 0x110C, 1}, {0x110E, 0x1112, 1}, {0x113C, 0x1140, 2}, {0x114C, 0x1150, 2}, {0x1154, 0x1155, 1}, {0x1159, 0x1159, 1}, {0x115F, 0x1161, 1}, {0x1163, 0x1169, 2}, {0x116D, 0x116E, 1}, {0x1172, 0x1173, 1}, {0x1175, 0x119E, 0x119E - 0x1175}, {0x11A8, 0x11AB, 0x11AB - 0x11A8}, {0x11AE, 0x11AF, 1}, {0x11B7, 0x11B8, 1}, {0x11BA, 0x11BA, 1}, {0x11BC, 0x11C2, 1}, {0x11EB, 0x11F0, 0x11F0 - 0x11EB}, {0x11F9, 0x11F9, 1}, {0x1E00, 0x1E9B, 1}, {0x1EA0, 0x1EF9, 1}, {0x1F00, 0x1F15, 1}, {0x1F18, 0x1F1D, 1}, {0x1F20, 0x1F45, 1}, {0x1F48, 0x1F4D, 1}, {0x1F50, 0x1F57, 1}, {0x1F59, 0x1F5B, 0x1F5B - 0x1F59}, {0x1F5D, 0x1F5D, 1}, {0x1F5F, 0x1F7D, 1}, {0x1F80, 0x1FB4, 1}, {0x1FB6, 0x1FBC, 1}, {0x1FBE, 0x1FBE, 1}, {0x1FC2, 0x1FC4, 1}, {0x1FC6, 0x1FCC, 1}, {0x1FD0, 0x1FD3, 1}, {0x1FD6, 0x1FDB, 1}, {0x1FE0, 0x1FEC, 1}, {0x1FF2, 0x1FF4, 1}, {0x1FF6, 0x1FFC, 1}, {0x2126, 0x2126, 1}, {0x212A, 0x212B, 1}, {0x212E, 0x212E, 1}, {0x2180, 0x2182, 1}, {0x3007, 0x3007, 1}, {0x3021, 0x3029, 1}, {0x3041, 0x3094, 1}, {0x30A1, 0x30FA, 1}, {0x3105, 0x312C, 1}, {0x4E00, 0x9FA5, 1}, {0xAC00, 0xD7A3, 1}, },}var second = &unicode.RangeTable{R16: []unicode.Range16{ {0x002D, 0x002E, 1}, {0x0030, 0x0039, 1}, {0x00B7, 0x00B7, 1}, {0x02D0, 0x02D1, 1}, {0x0300, 0x0345, 1}, {0x0360, 0x0361, 1}, {0x0387, 0x0387, 1}, {0x0483, 0x0486, 1}, {0x0591, 0x05A1, 1}, {0x05A3, 0x05B9, 1}, {0x05BB, 0x05BD, 1}, {0x05BF, 0x05BF, 1}, {0x05C1, 0x05C2, 1}, {0x05C4, 0x0640, 0x0640 - 0x05C4}, {0x064B, 0x0652, 1}, {0x0660, 0x0669, 1}, {0x0670, 0x0670, 1}, {0x06D6, 0x06DC, 1}, {0x06DD, 0x06DF, 1}, {0x06E0, 0x06E4, 1}, {0x06E7, 0x06E8, 1}, {0x06EA, 0x06ED, 1}, {0x06F0, 0x06F9, 1}, {0x0901, 0x0903, 1}, {0x093C, 0x093C, 1}, {0x093E, 0x094C, 1}, {0x094D, 0x094D, 1}, {0x0951, 0x0954, 1}, {0x0962, 0x0963, 1}, {0x0966, 0x096F, 1}, {0x0981, 0x0983, 1}, {0x09BC, 0x09BC, 1}, {0x09BE, 0x09BF, 1}, {0x09C0, 0x09C4, 1}, {0x09C7, 0x09C8, 1}, {0x09CB, 0x09CD, 1}, {0x09D7, 0x09D7, 1}, {0x09E2, 0x09E3, 1}, {0x09E6, 0x09EF, 1}, {0x0A02, 0x0A3C, 0x3A}, {0x0A3E, 0x0A3F, 1}, {0x0A40, 0x0A42, 1}, {0x0A47, 0x0A48, 1}, {0x0A4B, 0x0A4D, 1}, {0x0A66, 0x0A6F, 1}, {0x0A70, 0x0A71, 1}, {0x0A81, 0x0A83, 1}, {0x0ABC, 0x0ABC, 1}, {0x0ABE, 0x0AC5, 1}, {0x0AC7, 0x0AC9, 1}, {0x0ACB, 0x0ACD, 1}, {0x0AE6, 0x0AEF, 1}, {0x0B01, 0x0B03, 1}, {0x0B3C, 0x0B3C, 1}, {0x0B3E, 0x0B43, 1}, {0x0B47, 0x0B48, 1}, {0x0B4B, 0x0B4D, 1}, {0x0B56, 0x0B57, 1}, {0x0B66, 0x0B6F, 1}, {0x0B82, 0x0B83, 1}, {0x0BBE, 0x0BC2, 1}, {0x0BC6, 0x0BC8, 1}, {0x0BCA, 0x0BCD, 1}, {0x0BD7, 0x0BD7, 1}, {0x0BE7, 0x0BEF, 1}, {0x0C01, 0x0C03, 1}, {0x0C3E, 0x0C44, 1}, {0x0C46, 0x0C48, 1}, {0x0C4A, 0x0C4D, 1}, {0x0C55, 0x0C56, 1}, {0x0C66, 0x0C6F, 1}, {0x0C82, 0x0C83, 1}, {0x0CBE, 0x0CC4, 1}, {0x0CC6, 0x0CC8, 1}, {0x0CCA, 0x0CCD, 1}, {0x0CD5, 0x0CD6, 1}, {0x0CE6, 0x0CEF, 1}, {0x0D02, 0x0D03, 1}, {0x0D3E, 0x0D43, 1}, {0x0D46, 0x0D48, 1}, {0x0D4A, 0x0D4D, 1}, {0x0D57, 0x0D57, 1}, {0x0D66, 0x0D6F, 1}, {0x0E31, 0x0E31, 1}, {0x0E34, 0x0E3A, 1}, {0x0E46, 0x0E46, 1}, {0x0E47, 0x0E4E, 1}, {0x0E50, 0x0E59, 1}, {0x0EB1, 0x0EB1, 1}, {0x0EB4, 0x0EB9, 1}, {0x0EBB, 0x0EBC, 1}, {0x0EC6, 0x0EC6, 1}, {0x0EC8, 0x0ECD, 1}, {0x0ED0, 0x0ED9, 1}, {0x0F18, 0x0F19, 1}, {0x0F20, 0x0F29, 1}, {0x0F35, 0x0F39, 2}, {0x0F3E, 0x0F3F, 1}, {0x0F71, 0x0F84, 1}, {0x0F86, 0x0F8B, 1}, {0x0F90, 0x0F95, 1}, {0x0F97, 0x0F97, 1}, {0x0F99, 0x0FAD, 1}, {0x0FB1, 0x0FB7, 1}, {0x0FB9, 0x0FB9, 1}, {0x20D0, 0x20DC, 1}, {0x20E1, 0x3005, 0x3005 - 0x20E1}, {0x302A, 0x302F, 1}, {0x3031, 0x3035, 1}, {0x3099, 0x309A, 1}, {0x309D, 0x309E, 1}, {0x30FC, 0x30FE, 1}, },}// HTMLEntity is an entity map containing translations for the// standard HTML entity characters.//// See the [Decoder.Strict] and [Decoder.Entity] fields' documentation.varHTMLEntitymap[string]string = htmlEntityvar htmlEntity = map[string]string{/* hget http://www.w3.org/TR/html4/sgml/entities.html | ssam ' ,y /\>/ x/\<(.|\n)+/ s/\n/ /g ,x v/^\<!ENTITY/d ,s/\<!ENTITY ([^ ]+) .*U\+([0-9A-F][0-9A-F][0-9A-F][0-9A-F]) .+/ "\1": "\\u\2",/g ' */"nbsp": "\u00A0","iexcl": "\u00A1","cent": "\u00A2","pound": "\u00A3","curren": "\u00A4","yen": "\u00A5","brvbar": "\u00A6","sect": "\u00A7","uml": "\u00A8","copy": "\u00A9","ordf": "\u00AA","laquo": "\u00AB","not": "\u00AC","shy": "\u00AD","reg": "\u00AE","macr": "\u00AF","deg": "\u00B0","plusmn": "\u00B1","sup2": "\u00B2","sup3": "\u00B3","acute": "\u00B4","micro": "\u00B5","para": "\u00B6","middot": "\u00B7","cedil": "\u00B8","sup1": "\u00B9","ordm": "\u00BA","raquo": "\u00BB","frac14": "\u00BC","frac12": "\u00BD","frac34": "\u00BE","iquest": "\u00BF","Agrave": "\u00C0","Aacute": "\u00C1","Acirc": "\u00C2","Atilde": "\u00C3","Auml": "\u00C4","Aring": "\u00C5","AElig": "\u00C6","Ccedil": "\u00C7","Egrave": "\u00C8","Eacute": "\u00C9","Ecirc": "\u00CA","Euml": "\u00CB","Igrave": "\u00CC","Iacute": "\u00CD","Icirc": "\u00CE","Iuml": "\u00CF","ETH": "\u00D0","Ntilde": "\u00D1","Ograve": "\u00D2","Oacute": "\u00D3","Ocirc": "\u00D4","Otilde": "\u00D5","Ouml": "\u00D6","times": "\u00D7","Oslash": "\u00D8","Ugrave": "\u00D9","Uacute": "\u00DA","Ucirc": "\u00DB","Uuml": "\u00DC","Yacute": "\u00DD","THORN": "\u00DE","szlig": "\u00DF","agrave": "\u00E0","aacute": "\u00E1","acirc": "\u00E2","atilde": "\u00E3","auml": "\u00E4","aring": "\u00E5","aelig": "\u00E6","ccedil": "\u00E7","egrave": "\u00E8","eacute": "\u00E9","ecirc": "\u00EA","euml": "\u00EB","igrave": "\u00EC","iacute": "\u00ED","icirc": "\u00EE","iuml": "\u00EF","eth": "\u00F0","ntilde": "\u00F1","ograve": "\u00F2","oacute": "\u00F3","ocirc": "\u00F4","otilde": "\u00F5","ouml": "\u00F6","divide": "\u00F7","oslash": "\u00F8","ugrave": "\u00F9","uacute": "\u00FA","ucirc": "\u00FB","uuml": "\u00FC","yacute": "\u00FD","thorn": "\u00FE","yuml": "\u00FF","fnof": "\u0192","Alpha": "\u0391","Beta": "\u0392","Gamma": "\u0393","Delta": "\u0394","Epsilon": "\u0395","Zeta": "\u0396","Eta": "\u0397","Theta": "\u0398","Iota": "\u0399","Kappa": "\u039A","Lambda": "\u039B","Mu": "\u039C","Nu": "\u039D","Xi": "\u039E","Omicron": "\u039F","Pi": "\u03A0","Rho": "\u03A1","Sigma": "\u03A3","Tau": "\u03A4","Upsilon": "\u03A5","Phi": "\u03A6","Chi": "\u03A7","Psi": "\u03A8","Omega": "\u03A9","alpha": "\u03B1","beta": "\u03B2","gamma": "\u03B3","delta": "\u03B4","epsilon": "\u03B5","zeta": "\u03B6","eta": "\u03B7","theta": "\u03B8","iota": "\u03B9","kappa": "\u03BA","lambda": "\u03BB","mu": "\u03BC","nu": "\u03BD","xi": "\u03BE","omicron": "\u03BF","pi": "\u03C0","rho": "\u03C1","sigmaf": "\u03C2","sigma": "\u03C3","tau": "\u03C4","upsilon": "\u03C5","phi": "\u03C6","chi": "\u03C7","psi": "\u03C8","omega": "\u03C9","thetasym": "\u03D1","upsih": "\u03D2","piv": "\u03D6","bull": "\u2022","hellip": "\u2026","prime": "\u2032","Prime": "\u2033","oline": "\u203E","frasl": "\u2044","weierp": "\u2118","image": "\u2111","real": "\u211C","trade": "\u2122","alefsym": "\u2135","larr": "\u2190","uarr": "\u2191","rarr": "\u2192","darr": "\u2193","harr": "\u2194","crarr": "\u21B5","lArr": "\u21D0","uArr": "\u21D1","rArr": "\u21D2","dArr": "\u21D3","hArr": "\u21D4","forall": "\u2200","part": "\u2202","exist": "\u2203","empty": "\u2205","nabla": "\u2207","isin": "\u2208","notin": "\u2209","ni": "\u220B","prod": "\u220F","sum": "\u2211","minus": "\u2212","lowast": "\u2217","radic": "\u221A","prop": "\u221D","infin": "\u221E","ang": "\u2220","and": "\u2227","or": "\u2228","cap": "\u2229","cup": "\u222A","int": "\u222B","there4": "\u2234","sim": "\u223C","cong": "\u2245","asymp": "\u2248","ne": "\u2260","equiv": "\u2261","le": "\u2264","ge": "\u2265","sub": "\u2282","sup": "\u2283","nsub": "\u2284","sube": "\u2286","supe": "\u2287","oplus": "\u2295","otimes": "\u2297","perp": "\u22A5","sdot": "\u22C5","lceil": "\u2308","rceil": "\u2309","lfloor": "\u230A","rfloor": "\u230B","lang": "\u2329","rang": "\u232A","loz": "\u25CA","spades": "\u2660","clubs": "\u2663","hearts": "\u2665","diams": "\u2666","quot": "\u0022","amp": "\u0026","lt": "\u003C","gt": "\u003E","OElig": "\u0152","oelig": "\u0153","Scaron": "\u0160","scaron": "\u0161","Yuml": "\u0178","circ": "\u02C6","tilde": "\u02DC","ensp": "\u2002","emsp": "\u2003","thinsp": "\u2009","zwnj": "\u200C","zwj": "\u200D","lrm": "\u200E","rlm": "\u200F","ndash": "\u2013","mdash": "\u2014","lsquo": "\u2018","rsquo": "\u2019","sbquo": "\u201A","ldquo": "\u201C","rdquo": "\u201D","bdquo": "\u201E","dagger": "\u2020","Dagger": "\u2021","permil": "\u2030","lsaquo": "\u2039","rsaquo": "\u203A","euro": "\u20AC",}// HTMLAutoClose is the set of HTML elements that// should be considered to close automatically.//// See the [Decoder.Strict] and [Decoder.Entity] fields' documentation.varHTMLAutoClose []string = htmlAutoClosevar htmlAutoClose = []string{/* hget http://www.w3.org/TR/html4/loose.dtd | 9 sed -n 's/<!ELEMENT ([^ ]*) +- O EMPTY.+/ "\1",/p' | tr A-Z a-z */"basefont","br","area","link","img","param","hr","input","col","frame","isindex","base","meta",}var ( escQuot = []byte(""") // shorter than """ escApos = []byte("'") // shorter than "'" escAmp = []byte("&") escLT = []byte("<") escGT = []byte(">") escTab = []byte("	") escNL = []byte("
") escCR = []byte("
") escFFFD = []byte("\uFFFD") // Unicode replacement character)// EscapeText writes to w the properly escaped XML equivalent// of the plain text data s.func ( io.Writer, []byte) error {returnescapeText(, , true)}// escapeText writes to w the properly escaped XML equivalent// of the plain text data s. If escapeNewline is true, newline// characters will be escaped.func escapeText( io.Writer, []byte, bool) error {var []byte := 0for := 0; < len(); { , := utf8.DecodeRune([:]) += switch {case'"': = escQuotcase'\'': = escAposcase'&': = escAmpcase'<': = escLTcase'>': = escGTcase'\t': = escTabcase'\n':if ! {continue } = escNLcase'\r': = escCRdefault:if !isInCharacterRange() || ( == 0xFFFD && == 1) { = escFFFDbreak }continue }if , := .Write([ : -]); != nil {return }if , := .Write(); != nil {return } = } , := .Write([:])return}// EscapeString writes to p the properly escaped XML equivalent// of the plain text data s.func ( *printer) ( string) {var []byte := 0for := 0; < len(); { , := utf8.DecodeRuneInString([:]) += switch {case'"': = escQuotcase'\'': = escAposcase'&': = escAmpcase'<': = escLTcase'>': = escGTcase'\t': = escTabcase'\n': = escNLcase'\r': = escCRdefault:if !isInCharacterRange() || ( == 0xFFFD && == 1) { = escFFFDbreak }continue } .WriteString([ : -]) .Write() = } .WriteString([:])}// Escape is like [EscapeText] but omits the error return value.// It is provided for backwards compatibility with Go 1.0.// Code targeting Go 1.1 or later should use [EscapeText].func ( io.Writer, []byte) {EscapeText(, )}var ( cdataStart = []byte("<![CDATA[") cdataEnd = []byte("]]>") cdataEscape = []byte("]]]]><![CDATA[>"))// emitCDATA writes to w the CDATA-wrapped plain text data s.// It escapes CDATA directives nested in s.func emitCDATA( io.Writer, []byte) error {iflen() == 0 {returnnil }if , := .Write(cdataStart); != nil {return }for { , , := bytes.Cut(, cdataEnd)if ! {break }// Found a nested CDATA directive end.if , := .Write(); != nil {return }if , := .Write(cdataEscape); != nil {return } = }if , := .Write(); != nil {return } , := .Write(cdataEnd)return}// procInst parses the `param="..."` or `param='...'`// value out of the provided string, returning "" if not found.func procInst(, string) string {// TODO: this parsing is somewhat lame and not exact. // It works for all actual cases, though. = + "=" := len() := 0varbytefor < len() { := [:] := strings.Index(, )if < 0 || + >= len() {return"" } += + + 1if := [+]; == '\'' || == '"' { = break } }if == 0 {return"" } := strings.IndexByte([:], )if < 0 {return"" }return [ : +]}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.