├── .gitignore ├── bin ├── stockprice ├── stockprice-lin └── stockprice.exe ├── vendor ├── github.com │ ├── PuerkitoBio │ │ └── goquery │ │ │ ├── .gitattributes │ │ │ ├── .travis.yml │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── iteration.go │ │ │ ├── expand.go │ │ │ ├── query.go │ │ │ ├── array.go │ │ │ ├── type.go │ │ │ ├── doc.go │ │ │ ├── utilities.go │ │ │ ├── filter.go │ │ │ ├── property.go │ │ │ ├── README.md │ │ │ └── manipulation.go │ └── andybalholm │ │ └── cascadia │ │ ├── .travis.yml │ │ ├── README.md │ │ ├── LICENSE │ │ ├── selector.go │ │ └── parser.go └── golang.org │ └── x │ └── net │ ├── AUTHORS │ ├── CONTRIBUTORS │ ├── PATENTS │ ├── LICENSE │ └── html │ ├── atom │ ├── atom.go │ ├── gen.go │ └── table.go │ ├── const.go │ ├── doc.go │ ├── doctype.go │ ├── node.go │ ├── escape.go │ ├── foreign.go │ └── render.go ├── colorized_stockprice.png ├── Godeps ├── Readme └── Godeps.json ├── install.sh ├── finance ├── stockprice.go └── stockprice_test.go ├── main.go ├── README.md └── stubs └── sample_stock_price.html /.gitignore: -------------------------------------------------------------------------------- 1 | stockprice 2 | -------------------------------------------------------------------------------- /bin/stockprice: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simsalabim/stockprice/HEAD/bin/stockprice -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/.gitattributes: -------------------------------------------------------------------------------- 1 | testdata/* linguist-vendored 2 | -------------------------------------------------------------------------------- /bin/stockprice-lin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simsalabim/stockprice/HEAD/bin/stockprice-lin -------------------------------------------------------------------------------- /bin/stockprice.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simsalabim/stockprice/HEAD/bin/stockprice.exe -------------------------------------------------------------------------------- /colorized_stockprice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simsalabim/stockprice/HEAD/colorized_stockprice.png -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.1 5 | - 1.2 6 | - 1.3 7 | - 1.4 8 | - 1.5 9 | - 1.6 10 | - 1.7 11 | - tip 12 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/AUTHORS: -------------------------------------------------------------------------------- 1 | # This source code refers to The Go Authors for copyright purposes. 2 | # The master list of authors is in the main Go distribution, 3 | # visible at http://tip.golang.org/AUTHORS. 4 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This source code was written by the Go contributors. 2 | # The master list of contributors is in the main Go distribution, 3 | # visible at http://tip.golang.org/CONTRIBUTORS. 4 | -------------------------------------------------------------------------------- /vendor/github.com/andybalholm/cascadia/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | - 1.4 6 | 7 | install: 8 | - go get github.com/andybalholm/cascadia 9 | 10 | script: 11 | - go test -v 12 | 13 | notifications: 14 | email: false 15 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/.gitignore: -------------------------------------------------------------------------------- 1 | # editor temporary files 2 | *.sublime-* 3 | .DS_Store 4 | *.swp 5 | #*.*# 6 | tags 7 | 8 | # direnv config 9 | .env* 10 | 11 | # test binaries 12 | *.test 13 | 14 | # coverage and profilte outputs 15 | *.out 16 | 17 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ `uname -s` == "Darwin" ];then 3 | curl -L -s https://github.com/simsalabim/stockprice/raw/master/bin/stockprice -o /usr/local/bin/stockprice 4 | elif [ `uname -s` == "Linux"];then 5 | curl -L -s https://github.com/simsalabim/stockprice/raw/master/bin/stockprice-lin -o /usr/local/bin/stockprice 6 | fi 7 | chmod a+x /usr/local/bin/stockprice 8 | echo "\`stockprice\` is now installed and available on your system." 9 | -------------------------------------------------------------------------------- /vendor/github.com/andybalholm/cascadia/README.md: -------------------------------------------------------------------------------- 1 | # cascadia 2 | 3 | [![](https://travis-ci.org/andybalholm/cascadia.svg)](https://travis-ci.org/andybalholm/cascadia) 4 | 5 | The Cascadia package implements CSS selectors for use with the parse trees produced by the html package. 6 | 7 | To test CSS selectors without writing Go code, check out [cascadia](https://github.com/suntong/cascadia) the command line tool, a thin wrapper around this package. 8 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "stockprice", 3 | "GoVersion": "go1.8", 4 | "GodepVersion": "v79", 5 | "Deps": [ 6 | { 7 | "ImportPath": "github.com/PuerkitoBio/goquery", 8 | "Comment": "v1.1.0", 9 | "Rev": "e1271ee34c6a305e38566ecd27ae374944907ee9" 10 | }, 11 | { 12 | "ImportPath": "github.com/andybalholm/cascadia", 13 | "Rev": "349dd0209470eabd9514242c688c403c0926d266" 14 | }, 15 | { 16 | "ImportPath": "github.com/simsalabim/stockprice/finance", 17 | "Rev": "072869e248dcfb6daffdf8f5790bd8162f02175b" 18 | }, 19 | { 20 | "ImportPath": "golang.org/x/net/html", 21 | "Rev": "d379faa25cbdc04d653984913a2ceb43b0bc46d7" 22 | }, 23 | { 24 | "ImportPath": "golang.org/x/net/html/atom", 25 | "Rev": "d379faa25cbdc04d653984913a2ceb43b0bc46d7" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /finance/stockprice.go: -------------------------------------------------------------------------------- 1 | package finance 2 | 3 | import ( 4 | "errors" 5 | "github.com/PuerkitoBio/goquery" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func FindStockPrice(symbol string) (float64, float64, string, error) { 12 | return findStockPriceByUrl(stockPriceUrl(symbol)) 13 | } 14 | 15 | func findStockPriceByUrl(stockPriceUrl string) (float64, float64, string, error) { 16 | doc, err := goquery.NewDocument(stockPriceUrl) 17 | if err != nil { 18 | return -1, -1, "", errors.New("Your search produces no matches.") 19 | } 20 | 21 | selection := doc.Find(".wsod_last span") 22 | 23 | if len(selection.Nodes) == 0 { 24 | return -2, -2, "", errors.New("Your search produces no matches.") 25 | } 26 | 27 | changes := doc.Find(".wsod_change span") 28 | percentageStr := changes.Eq(5).Text() 29 | delta, _ := strconv.ParseFloat(strings.Replace(changes.Eq(1).Text(), ",", "", -1), 64) 30 | 31 | stockPriceStr := strings.TrimSpace(strings.Replace(selection.Text(), ",", "", -1)) 32 | stockPrice, err := strconv.ParseFloat(stockPriceStr, 64) 33 | if err != nil { 34 | return -3, -3, "", err 35 | } 36 | 37 | return stockPrice, delta, percentageStr, nil 38 | } 39 | 40 | func stockPriceUrl(symbol string) string { 41 | return "http://money.cnn.com/quote/quote.html?symb=" + url.QueryEscape(symbol) 42 | } 43 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /vendor/github.com/andybalholm/cascadia/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Andy Balholm. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016, Martin Angers & Contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/iteration.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | // Each iterates over a Selection object, executing a function for each 4 | // matched element. It returns the current Selection object. The function 5 | // f is called for each element in the selection with the index of the 6 | // element in that selection starting at 0, and a *Selection that contains 7 | // only that element. 8 | func (s *Selection) Each(f func(int, *Selection)) *Selection { 9 | for i, n := range s.Nodes { 10 | f(i, newSingleSelection(n, s.document)) 11 | } 12 | return s 13 | } 14 | 15 | // EachWithBreak iterates over a Selection object, executing a function for each 16 | // matched element. It is identical to Each except that it is possible to break 17 | // out of the loop by returning false in the callback function. It returns the 18 | // current Selection object. 19 | func (s *Selection) EachWithBreak(f func(int, *Selection) bool) *Selection { 20 | for i, n := range s.Nodes { 21 | if !f(i, newSingleSelection(n, s.document)) { 22 | return s 23 | } 24 | } 25 | return s 26 | } 27 | 28 | // Map passes each element in the current matched set through a function, 29 | // producing a slice of string holding the returned values. The function 30 | // f is called for each element in the selection with the index of the 31 | // element in that selection starting at 0, and a *Selection that contains 32 | // only that element. 33 | func (s *Selection) Map(f func(int, *Selection) string) (result []string) { 34 | for i, n := range s.Nodes { 35 | result = append(result, f(i, newSingleSelection(n, s.document))) 36 | } 37 | 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /finance/stockprice_test.go: -------------------------------------------------------------------------------- 1 | package finance 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | const checkMark = "\u2713" 12 | const ballotX = "\u2717" 13 | 14 | func TestFindStockPriceByUrl(t *testing.T) { 15 | ms := mockServer("../stubs/sample_stock_price.html") 16 | defer ms.Close() 17 | 18 | testStockPrice, testDelta, testPercentage, _ := findStockPriceByUrl(ms.URL) 19 | 20 | expectedPrice := 1004.0203092013 21 | if testStockPrice == expectedPrice { 22 | t.Logf("\tShould find a correct stock price \"%f\" %v", expectedPrice, checkMark) 23 | } else { 24 | t.Errorf("\tShould find a correct stock price \"%f\", but found \"%f\" %v", 25 | expectedPrice, testStockPrice, ballotX) 26 | } 27 | 28 | expectedDelta := 1000.03 29 | if testDelta == expectedDelta { 30 | t.Logf("\tShould find a correct price change \"%f\" %v", expectedDelta, checkMark) 31 | } else { 32 | t.Errorf("\tShould find a correct price change \"%f\", but found \"%f\" %v", 33 | expectedDelta, testDelta, ballotX) 34 | } 35 | 36 | expectedPercentage := "+0.74%" 37 | if testPercentage == expectedPercentage { 38 | t.Logf("\tShould find a correct percentage change \"%f\" %v", expectedPercentage, checkMark) 39 | } else { 40 | t.Errorf("\tShould find a correct percentage change \"%f\", but found \"%f\" %v", 41 | expectedPercentage, testPercentage, ballotX) 42 | } 43 | 44 | ms.Close() 45 | } 46 | 47 | func mockServer(stubbedHtmlFile string) *httptest.Server { 48 | f := func(w http.ResponseWriter, r *http.Request) { 49 | feed, _ := ioutil.ReadFile(stubbedHtmlFile) 50 | w.WriteHeader(200) 51 | w.Header().Set("Content-Type", "application/xml") 52 | fmt.Fprintln(w, string(feed)) 53 | } 54 | 55 | return httptest.NewServer(http.HandlerFunc(f)) 56 | } 57 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/expand.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import "golang.org/x/net/html" 4 | 5 | // Add adds the selector string's matching nodes to those in the current 6 | // selection and returns a new Selection object. 7 | // The selector string is run in the context of the document of the current 8 | // Selection object. 9 | func (s *Selection) Add(selector string) *Selection { 10 | return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, compileMatcher(selector))...) 11 | } 12 | 13 | // AddMatcher adds the matcher's matching nodes to those in the current 14 | // selection and returns a new Selection object. 15 | // The matcher is run in the context of the document of the current 16 | // Selection object. 17 | func (s *Selection) AddMatcher(m Matcher) *Selection { 18 | return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, m)...) 19 | } 20 | 21 | // AddSelection adds the specified Selection object's nodes to those in the 22 | // current selection and returns a new Selection object. 23 | func (s *Selection) AddSelection(sel *Selection) *Selection { 24 | if sel == nil { 25 | return s.AddNodes() 26 | } 27 | return s.AddNodes(sel.Nodes...) 28 | } 29 | 30 | // Union is an alias for AddSelection. 31 | func (s *Selection) Union(sel *Selection) *Selection { 32 | return s.AddSelection(sel) 33 | } 34 | 35 | // AddNodes adds the specified nodes to those in the 36 | // current selection and returns a new Selection object. 37 | func (s *Selection) AddNodes(nodes ...*html.Node) *Selection { 38 | return pushStack(s, appendWithoutDuplicates(s.Nodes, nodes, nil)) 39 | } 40 | 41 | // AndSelf adds the previous set of elements on the stack to the current set. 42 | // It returns a new Selection object containing the current Selection combined 43 | // with the previous one. 44 | func (s *Selection) AndSelf() *Selection { 45 | return s.AddSelection(s.prevSel) 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/query.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import "golang.org/x/net/html" 4 | 5 | // Is checks the current matched set of elements against a selector and 6 | // returns true if at least one of these elements matches. 7 | func (s *Selection) Is(selector string) bool { 8 | if len(s.Nodes) > 0 { 9 | return s.IsMatcher(compileMatcher(selector)) 10 | } 11 | 12 | return false 13 | } 14 | 15 | // IsMatcher checks the current matched set of elements against a matcher and 16 | // returns true if at least one of these elements matches. 17 | func (s *Selection) IsMatcher(m Matcher) bool { 18 | if len(s.Nodes) > 0 { 19 | if len(s.Nodes) == 1 { 20 | return m.Match(s.Nodes[0]) 21 | } 22 | return len(m.Filter(s.Nodes)) > 0 23 | } 24 | 25 | return false 26 | } 27 | 28 | // IsFunction checks the current matched set of elements against a predicate and 29 | // returns true if at least one of these elements matches. 30 | func (s *Selection) IsFunction(f func(int, *Selection) bool) bool { 31 | return s.FilterFunction(f).Length() > 0 32 | } 33 | 34 | // IsSelection checks the current matched set of elements against a Selection object 35 | // and returns true if at least one of these elements matches. 36 | func (s *Selection) IsSelection(sel *Selection) bool { 37 | return s.FilterSelection(sel).Length() > 0 38 | } 39 | 40 | // IsNodes checks the current matched set of elements against the specified nodes 41 | // and returns true if at least one of these elements matches. 42 | func (s *Selection) IsNodes(nodes ...*html.Node) bool { 43 | return s.FilterNodes(nodes...).Length() > 0 44 | } 45 | 46 | // Contains returns true if the specified Node is within, 47 | // at any depth, one of the nodes in the Selection object. 48 | // It is NOT inclusive, to behave like jQuery's implementation, and 49 | // unlike Javascript's .contains, so if the contained 50 | // node is itself in the selection, it returns false. 51 | func (s *Selection) Contains(n *html.Node) bool { 52 | return sliceContains(s.Nodes, n) 53 | } 54 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "stockprice/finance" 8 | ) 9 | 10 | var format = flag.String("f", "term", "formatting: term or vim") 11 | 12 | func init() { 13 | flag.Parse() 14 | if len(flag.Args()) != 1 { 15 | fmt.Println("Usage: stockprice [symbol], e.g. stockprice grpn") 16 | os.Exit(1) 17 | } 18 | } 19 | 20 | func main() { 21 | price, delta, percentage, err := finance.FindStockPrice(flag.Args()[0]) 22 | if err != nil { 23 | fmt.Println(err.Error()) 24 | return 25 | } 26 | 27 | var result string 28 | 29 | if *format == "vim" { 30 | result = formatForVim(price, delta, percentage) 31 | } else if *format == "term" { 32 | result = formatForTerminal(price, delta, percentage) 33 | } 34 | 35 | fmt.Println(result) 36 | } 37 | 38 | func formatForTerminal(price float64, delta float64, percentage string) string { 39 | var deltaFormatted string 40 | var percentageFormatted = percentage 41 | 42 | if delta > 0 { 43 | deltaFormatted = fmt.Sprintf("\x1b[32m+%.2f\x1b[0m", delta) 44 | percentageFormatted = fmt.Sprintf("\x1b[32m%s\x1b[0m", percentage) 45 | } else if delta < 0 { 46 | deltaFormatted = fmt.Sprintf("\x1b[31m%.2f\x1b[0m", delta) 47 | percentageFormatted = fmt.Sprintf("\x1b[31m%s\x1b[0m", percentage) 48 | } else { 49 | deltaFormatted = fmt.Sprintf("%.2f", delta) 50 | } 51 | 52 | return fmt.Sprintf("%.2f %s %s", price, deltaFormatted, percentageFormatted) 53 | } 54 | 55 | func formatForVim(price float64, delta float64, percentage string) string { 56 | var result string 57 | if delta > 0 { 58 | result = fmt.Sprintf(`echohl Normal 59 | echo "%.2f" 60 | echohl MoreMsg 61 | echon " +%.2f %s" 62 | echohl Normal`, price, delta, percentage) 63 | 64 | } else if delta < 0 { 65 | result = fmt.Sprintf(`echohl Normal 66 | echo "%.2f" 67 | echohl WarningMsg 68 | echon " %.2f %s" 69 | echohl Normal`, price, delta, percentage) 70 | } else { 71 | result = fmt.Sprintf(`echohl Normal 72 | echo "%.2f" 73 | echon " %.2f %s" 74 | echohl Normal`, price, delta, percentage) 75 | } 76 | return result 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stock Price Reporter 2 | 3 | A CLI tool that looks up stock prices. 4 | Now you can check stock price right in your console or in VIM without losing focus on what matters :computer: 5 | ![](https://raw.githubusercontent.com/simsalabim/stockprice/master/colorized_stockprice.png?cache) 6 | 7 | ```shell 8 | $ stockprice grpn 9 | 4.10 +0.03 (0.74%) 10 | ``` 11 | 12 | ## Installation 13 | #### `curl -L https://raw.githubusercontent.com/simsalabim/stockprice/master/install.sh | sh` 14 | 15 | Or manually download an executable for your OS: [OSX](https://github.com/simsalabim/stockprice/blob/master/bin/stockprice), [Linux](https://github.com/simsalabim/stockprice/blob/master/bin/stockprice-lin), [Windows](https://github.com/simsalabim/stockprice/blob/master/bin/stockprice.exe) and place it in a directory listed in $PATH. 16 | 17 | ## VIM Integration 18 | As amazing as it sounds, you may now check the stock price of your interest right in your code editor. 19 | 20 | ```vim 21 | " ~/.vimrc 22 | function! Stockprice(symbol) 23 | execute system('stockprice -f vim ' . a:symbol) 24 | endfunction 25 | 26 | map fb :call Stockprice('fb') 27 | ``` 28 | 29 | ## Alternative VIM Integration 30 | If you're only interested in VIM integration and not in the system binary, the following web version that 31 | uses this very [package](https://github.com/simsalabim/rebot.works/blob/069fdf0cb33169026b4fea83483747f9f67af23e/main.go#L6) might work for you: 32 | ```vim 33 | function! Stockprice(symbol) 34 | echo system("curl -s http://rebot.works/stockprice/" . a:symbol) 35 | endfunction 36 | ``` 37 | 38 | ## More fun 39 | What can be more fun than system aliases? You can create some for frequently checked stocks: 40 | ```shell 41 | # .zshrc/.bashrc 42 | alias fb="stockprice fb" 43 | 44 | # further usage in console: 45 | $ fb 46 | 137.72 +0.42 (0.31%) 47 | ``` 48 | 49 | ## Development 50 | 51 | For development you will need a Go language compiler installed. I used Go v1.8. 52 | 53 | For dependency management, please refer to [Godep](https://github.com/tools/godep) documentation. 54 | 55 | To rebuild the binaries for 3 main platforms run `./build_executables`. 56 | 57 | Run tests with `cd finance && go test -v` 58 | 59 | ## TODO 60 | - [ ] Atom plugin 61 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/atom/atom.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package atom provides integer codes (also known as atoms) for a fixed set of 6 | // frequently occurring HTML strings: tag names and attribute keys such as "p" 7 | // and "id". 8 | // 9 | // Sharing an atom's name between all elements with the same tag can result in 10 | // fewer string allocations when tokenizing and parsing HTML. Integer 11 | // comparisons are also generally faster than string comparisons. 12 | // 13 | // The value of an atom's particular code is not guaranteed to stay the same 14 | // between versions of this package. Neither is any ordering guaranteed: 15 | // whether atom.H1 < atom.H2 may also change. The codes are not guaranteed to 16 | // be dense. The only guarantees are that e.g. looking up "div" will yield 17 | // atom.Div, calling atom.Div.String will return "div", and atom.Div != 0. 18 | package atom 19 | 20 | // Atom is an integer code for a string. The zero value maps to "". 21 | type Atom uint32 22 | 23 | // String returns the atom's name. 24 | func (a Atom) String() string { 25 | start := uint32(a >> 8) 26 | n := uint32(a & 0xff) 27 | if start+n > uint32(len(atomText)) { 28 | return "" 29 | } 30 | return atomText[start : start+n] 31 | } 32 | 33 | func (a Atom) string() string { 34 | return atomText[a>>8 : a>>8+a&0xff] 35 | } 36 | 37 | // fnv computes the FNV hash with an arbitrary starting value h. 38 | func fnv(h uint32, s []byte) uint32 { 39 | for i := range s { 40 | h ^= uint32(s[i]) 41 | h *= 16777619 42 | } 43 | return h 44 | } 45 | 46 | func match(s string, t []byte) bool { 47 | for i, c := range t { 48 | if s[i] != c { 49 | return false 50 | } 51 | } 52 | return true 53 | } 54 | 55 | // Lookup returns the atom whose name is s. It returns zero if there is no 56 | // such atom. The lookup is case sensitive. 57 | func Lookup(s []byte) Atom { 58 | if len(s) == 0 || len(s) > maxAtomLen { 59 | return 0 60 | } 61 | h := fnv(hash0, s) 62 | if a := table[h&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { 63 | return a 64 | } 65 | if a := table[(h>>16)&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) { 66 | return a 67 | } 68 | return 0 69 | } 70 | 71 | // String returns a string whose contents are equal to s. In that sense, it is 72 | // equivalent to string(s) but may be more efficient. 73 | func String(s []byte) string { 74 | if a := Lookup(s); a != 0 { 75 | return a.String() 76 | } 77 | return string(s) 78 | } 79 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/const.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package html 6 | 7 | // Section 12.2.3.2 of the HTML5 specification says "The following elements 8 | // have varying levels of special parsing rules". 9 | // https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements 10 | var isSpecialElementMap = map[string]bool{ 11 | "address": true, 12 | "applet": true, 13 | "area": true, 14 | "article": true, 15 | "aside": true, 16 | "base": true, 17 | "basefont": true, 18 | "bgsound": true, 19 | "blockquote": true, 20 | "body": true, 21 | "br": true, 22 | "button": true, 23 | "caption": true, 24 | "center": true, 25 | "col": true, 26 | "colgroup": true, 27 | "dd": true, 28 | "details": true, 29 | "dir": true, 30 | "div": true, 31 | "dl": true, 32 | "dt": true, 33 | "embed": true, 34 | "fieldset": true, 35 | "figcaption": true, 36 | "figure": true, 37 | "footer": true, 38 | "form": true, 39 | "frame": true, 40 | "frameset": true, 41 | "h1": true, 42 | "h2": true, 43 | "h3": true, 44 | "h4": true, 45 | "h5": true, 46 | "h6": true, 47 | "head": true, 48 | "header": true, 49 | "hgroup": true, 50 | "hr": true, 51 | "html": true, 52 | "iframe": true, 53 | "img": true, 54 | "input": true, 55 | "isindex": true, 56 | "li": true, 57 | "link": true, 58 | "listing": true, 59 | "marquee": true, 60 | "menu": true, 61 | "meta": true, 62 | "nav": true, 63 | "noembed": true, 64 | "noframes": true, 65 | "noscript": true, 66 | "object": true, 67 | "ol": true, 68 | "p": true, 69 | "param": true, 70 | "plaintext": true, 71 | "pre": true, 72 | "script": true, 73 | "section": true, 74 | "select": true, 75 | "source": true, 76 | "style": true, 77 | "summary": true, 78 | "table": true, 79 | "tbody": true, 80 | "td": true, 81 | "template": true, 82 | "textarea": true, 83 | "tfoot": true, 84 | "th": true, 85 | "thead": true, 86 | "title": true, 87 | "tr": true, 88 | "track": true, 89 | "ul": true, 90 | "wbr": true, 91 | "xmp": true, 92 | } 93 | 94 | func isSpecialElement(element *Node) bool { 95 | switch element.Namespace { 96 | case "", "html": 97 | return isSpecialElementMap[element.Data] 98 | case "svg": 99 | return element.Data == "foreignObject" 100 | } 101 | return false 102 | } 103 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/array.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "golang.org/x/net/html" 5 | ) 6 | 7 | // First reduces the set of matched elements to the first in the set. 8 | // It returns a new Selection object, and an empty Selection object if the 9 | // the selection is empty. 10 | func (s *Selection) First() *Selection { 11 | return s.Eq(0) 12 | } 13 | 14 | // Last reduces the set of matched elements to the last in the set. 15 | // It returns a new Selection object, and an empty Selection object if 16 | // the selection is empty. 17 | func (s *Selection) Last() *Selection { 18 | return s.Eq(-1) 19 | } 20 | 21 | // Eq reduces the set of matched elements to the one at the specified index. 22 | // If a negative index is given, it counts backwards starting at the end of the 23 | // set. It returns a new Selection object, and an empty Selection object if the 24 | // index is invalid. 25 | func (s *Selection) Eq(index int) *Selection { 26 | if index < 0 { 27 | index += len(s.Nodes) 28 | } 29 | 30 | if index >= len(s.Nodes) || index < 0 { 31 | return newEmptySelection(s.document) 32 | } 33 | 34 | return s.Slice(index, index+1) 35 | } 36 | 37 | // Slice reduces the set of matched elements to a subset specified by a range 38 | // of indices. 39 | func (s *Selection) Slice(start, end int) *Selection { 40 | if start < 0 { 41 | start += len(s.Nodes) 42 | } 43 | if end < 0 { 44 | end += len(s.Nodes) 45 | } 46 | return pushStack(s, s.Nodes[start:end]) 47 | } 48 | 49 | // Get retrieves the underlying node at the specified index. 50 | // Get without parameter is not implemented, since the node array is available 51 | // on the Selection object. 52 | func (s *Selection) Get(index int) *html.Node { 53 | if index < 0 { 54 | index += len(s.Nodes) // Negative index gets from the end 55 | } 56 | return s.Nodes[index] 57 | } 58 | 59 | // Index returns the position of the first element within the Selection object 60 | // relative to its sibling elements. 61 | func (s *Selection) Index() int { 62 | if len(s.Nodes) > 0 { 63 | return newSingleSelection(s.Nodes[0], s.document).PrevAll().Length() 64 | } 65 | return -1 66 | } 67 | 68 | // IndexSelector returns the position of the first element within the 69 | // Selection object relative to the elements matched by the selector, or -1 if 70 | // not found. 71 | func (s *Selection) IndexSelector(selector string) int { 72 | if len(s.Nodes) > 0 { 73 | sel := s.document.Find(selector) 74 | return indexInSlice(sel.Nodes, s.Nodes[0]) 75 | } 76 | return -1 77 | } 78 | 79 | // IndexMatcher returns the position of the first element within the 80 | // Selection object relative to the elements matched by the matcher, or -1 if 81 | // not found. 82 | func (s *Selection) IndexMatcher(m Matcher) int { 83 | if len(s.Nodes) > 0 { 84 | sel := s.document.FindMatcher(m) 85 | return indexInSlice(sel.Nodes, s.Nodes[0]) 86 | } 87 | return -1 88 | } 89 | 90 | // IndexOfNode returns the position of the specified node within the Selection 91 | // object, or -1 if not found. 92 | func (s *Selection) IndexOfNode(node *html.Node) int { 93 | return indexInSlice(s.Nodes, node) 94 | } 95 | 96 | // IndexOfSelection returns the position of the first node in the specified 97 | // Selection object within this Selection object, or -1 if not found. 98 | func (s *Selection) IndexOfSelection(sel *Selection) int { 99 | if sel != nil && len(sel.Nodes) > 0 { 100 | return indexInSlice(s.Nodes, sel.Nodes[0]) 101 | } 102 | return -1 103 | } 104 | -------------------------------------------------------------------------------- /stubs/sample_stock_price.html: -------------------------------------------------------------------------------- 1 | FB - Facebook Inc Stock quote - CNNMoney.com 2 | 3 |

Facebook Inc (NASDAQ:FB)

1004.0203092013
Delayed Data
As of Jun 11
 +1000.03 / +0.74%
Today’s Change
144.56
Today|||52-Week Range
195.32
+8.55%
Year-to-Date
4 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package html implements an HTML5-compliant tokenizer and parser. 7 | 8 | Tokenization is done by creating a Tokenizer for an io.Reader r. It is the 9 | caller's responsibility to ensure that r provides UTF-8 encoded HTML. 10 | 11 | z := html.NewTokenizer(r) 12 | 13 | Given a Tokenizer z, the HTML is tokenized by repeatedly calling z.Next(), 14 | which parses the next token and returns its type, or an error: 15 | 16 | for { 17 | tt := z.Next() 18 | if tt == html.ErrorToken { 19 | // ... 20 | return ... 21 | } 22 | // Process the current token. 23 | } 24 | 25 | There are two APIs for retrieving the current token. The high-level API is to 26 | call Token; the low-level API is to call Text or TagName / TagAttr. Both APIs 27 | allow optionally calling Raw after Next but before Token, Text, TagName, or 28 | TagAttr. In EBNF notation, the valid call sequence per token is: 29 | 30 | Next {Raw} [ Token | Text | TagName {TagAttr} ] 31 | 32 | Token returns an independent data structure that completely describes a token. 33 | Entities (such as "<") are unescaped, tag names and attribute keys are 34 | lower-cased, and attributes are collected into a []Attribute. For example: 35 | 36 | for { 37 | if z.Next() == html.ErrorToken { 38 | // Returning io.EOF indicates success. 39 | return z.Err() 40 | } 41 | emitToken(z.Token()) 42 | } 43 | 44 | The low-level API performs fewer allocations and copies, but the contents of 45 | the []byte values returned by Text, TagName and TagAttr may change on the next 46 | call to Next. For example, to extract an HTML page's anchor text: 47 | 48 | depth := 0 49 | for { 50 | tt := z.Next() 51 | switch tt { 52 | case ErrorToken: 53 | return z.Err() 54 | case TextToken: 55 | if depth > 0 { 56 | // emitBytes should copy the []byte it receives, 57 | // if it doesn't process it immediately. 58 | emitBytes(z.Text()) 59 | } 60 | case StartTagToken, EndTagToken: 61 | tn, _ := z.TagName() 62 | if len(tn) == 1 && tn[0] == 'a' { 63 | if tt == StartTagToken { 64 | depth++ 65 | } else { 66 | depth-- 67 | } 68 | } 69 | } 70 | } 71 | 72 | Parsing is done by calling Parse with an io.Reader, which returns the root of 73 | the parse tree (the document element) as a *Node. It is the caller's 74 | responsibility to ensure that the Reader provides UTF-8 encoded HTML. For 75 | example, to process each anchor node in depth-first order: 76 | 77 | doc, err := html.Parse(r) 78 | if err != nil { 79 | // ... 80 | } 81 | var f func(*html.Node) 82 | f = func(n *html.Node) { 83 | if n.Type == html.ElementNode && n.Data == "a" { 84 | // Do something with n... 85 | } 86 | for c := n.FirstChild; c != nil; c = c.NextSibling { 87 | f(c) 88 | } 89 | } 90 | f(doc) 91 | 92 | The relevant specifications include: 93 | https://html.spec.whatwg.org/multipage/syntax.html and 94 | https://html.spec.whatwg.org/multipage/syntax.html#tokenization 95 | */ 96 | package html 97 | 98 | // The tokenization algorithm implemented by this package is not a line-by-line 99 | // transliteration of the relatively verbose state-machine in the WHATWG 100 | // specification. A more direct approach is used instead, where the program 101 | // counter implies the state, such as whether it is tokenizing a tag or a text 102 | // node. Specification compliance is verified by checking expected and actual 103 | // outputs over a test suite rather than aiming for algorithmic fidelity. 104 | 105 | // TODO(nigeltao): Does a DOM API belong in this package or a separate one? 106 | // TODO(nigeltao): How does parsing interact with a JavaScript engine? 107 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/type.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | 9 | "github.com/andybalholm/cascadia" 10 | 11 | "golang.org/x/net/html" 12 | ) 13 | 14 | // Document represents an HTML document to be manipulated. Unlike jQuery, which 15 | // is loaded as part of a DOM document, and thus acts upon its containing 16 | // document, GoQuery doesn't know which HTML document to act upon. So it needs 17 | // to be told, and that's what the Document class is for. It holds the root 18 | // document node to manipulate, and can make selections on this document. 19 | type Document struct { 20 | *Selection 21 | Url *url.URL 22 | rootNode *html.Node 23 | } 24 | 25 | // NewDocumentFromNode is a Document constructor that takes a root html Node 26 | // as argument. 27 | func NewDocumentFromNode(root *html.Node) *Document { 28 | return newDocument(root, nil) 29 | } 30 | 31 | // NewDocument is a Document constructor that takes a string URL as argument. 32 | // It loads the specified document, parses it, and stores the root Document 33 | // node, ready to be manipulated. 34 | func NewDocument(url string) (*Document, error) { 35 | // Load the URL 36 | res, e := http.Get(url) 37 | if e != nil { 38 | return nil, e 39 | } 40 | return NewDocumentFromResponse(res) 41 | } 42 | 43 | // NewDocumentFromReader returns a Document from a generic reader. 44 | // It returns an error as second value if the reader's data cannot be parsed 45 | // as html. It does *not* check if the reader is also an io.Closer, so the 46 | // provided reader is never closed by this call, it is the responsibility 47 | // of the caller to close it if required. 48 | func NewDocumentFromReader(r io.Reader) (*Document, error) { 49 | root, e := html.Parse(r) 50 | if e != nil { 51 | return nil, e 52 | } 53 | return newDocument(root, nil), nil 54 | } 55 | 56 | // NewDocumentFromResponse is another Document constructor that takes an http response as argument. 57 | // It loads the specified response's document, parses it, and stores the root Document 58 | // node, ready to be manipulated. The response's body is closed on return. 59 | func NewDocumentFromResponse(res *http.Response) (*Document, error) { 60 | if res == nil { 61 | return nil, errors.New("Response is nil") 62 | } 63 | defer res.Body.Close() 64 | if res.Request == nil { 65 | return nil, errors.New("Response.Request is nil") 66 | } 67 | 68 | // Parse the HTML into nodes 69 | root, e := html.Parse(res.Body) 70 | if e != nil { 71 | return nil, e 72 | } 73 | 74 | // Create and fill the document 75 | return newDocument(root, res.Request.URL), nil 76 | } 77 | 78 | // CloneDocument creates a deep-clone of a document. 79 | func CloneDocument(doc *Document) *Document { 80 | return newDocument(cloneNode(doc.rootNode), doc.Url) 81 | } 82 | 83 | // Private constructor, make sure all fields are correctly filled. 84 | func newDocument(root *html.Node, url *url.URL) *Document { 85 | // Create and fill the document 86 | d := &Document{nil, url, root} 87 | d.Selection = newSingleSelection(root, d) 88 | return d 89 | } 90 | 91 | // Selection represents a collection of nodes matching some criteria. The 92 | // initial Selection can be created by using Document.Find, and then 93 | // manipulated using the jQuery-like chainable syntax and methods. 94 | type Selection struct { 95 | Nodes []*html.Node 96 | document *Document 97 | prevSel *Selection 98 | } 99 | 100 | // Helper constructor to create an empty selection 101 | func newEmptySelection(doc *Document) *Selection { 102 | return &Selection{nil, doc, nil} 103 | } 104 | 105 | // Helper constructor to create a selection of only one node 106 | func newSingleSelection(node *html.Node, doc *Document) *Selection { 107 | return &Selection{[]*html.Node{node}, doc, nil} 108 | } 109 | 110 | // Matcher is an interface that defines the methods to match 111 | // HTML nodes against a compiled selector string. Cascadia's 112 | // Selector implements this interface. 113 | type Matcher interface { 114 | Match(*html.Node) bool 115 | MatchAll(*html.Node) []*html.Node 116 | Filter([]*html.Node) []*html.Node 117 | } 118 | 119 | // compileMatcher compiles the selector string s and returns 120 | // the corresponding Matcher. If s is an invalid selector string, 121 | // it returns a Matcher that fails all matches. 122 | func compileMatcher(s string) Matcher { 123 | cs, err := cascadia.Compile(s) 124 | if err != nil { 125 | return invalidMatcher{} 126 | } 127 | return cs 128 | } 129 | 130 | // invalidMatcher is a Matcher that always fails to match. 131 | type invalidMatcher struct{} 132 | 133 | func (invalidMatcher) Match(n *html.Node) bool { return false } 134 | func (invalidMatcher) MatchAll(n *html.Node) []*html.Node { return nil } 135 | func (invalidMatcher) Filter(ns []*html.Node) []*html.Node { return nil } 136 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2016, Martin Angers & Contributors 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, 5 | // are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright notice, 8 | // this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation and/or 11 | // other materials provided with the distribution. 12 | // * Neither the name of the author nor the names of its contributors may be used to 13 | // endorse or promote products derived from this software without specific prior written permission. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 16 | // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17 | // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 18 | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 22 | // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | /* 25 | Package goquery implements features similar to jQuery, including the chainable 26 | syntax, to manipulate and query an HTML document. 27 | 28 | It brings a syntax and a set of features similar to jQuery to the Go language. 29 | It is based on Go's net/html package and the CSS Selector library cascadia. 30 | Since the net/html parser returns nodes, and not a full-featured DOM 31 | tree, jQuery's stateful manipulation functions (like height(), css(), detach()) 32 | have been left off. 33 | 34 | Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is 35 | the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML. 36 | See the repository's wiki for various options on how to do this. 37 | 38 | Syntax-wise, it is as close as possible to jQuery, with the same method names when 39 | possible, and that warm and fuzzy chainable interface. jQuery being the 40 | ultra-popular library that it is, writing a similar HTML-manipulating 41 | library was better to follow its API than to start anew (in the same spirit as 42 | Go's fmt package), even though some of its methods are less than intuitive (looking 43 | at you, index()...). 44 | 45 | It is hosted on GitHub, along with additional documentation in the README.md 46 | file: https://github.com/puerkitobio/goquery 47 | 48 | Please note that because of the net/html dependency, goquery requires Go1.1+. 49 | 50 | The various methods are split into files based on the category of behavior. 51 | The three dots (...) indicate that various "overloads" are available. 52 | 53 | * array.go : array-like positional manipulation of the selection. 54 | - Eq() 55 | - First() 56 | - Get() 57 | - Index...() 58 | - Last() 59 | - Slice() 60 | 61 | * expand.go : methods that expand or augment the selection's set. 62 | - Add...() 63 | - AndSelf() 64 | - Union(), which is an alias for AddSelection() 65 | 66 | * filter.go : filtering methods, that reduce the selection's set. 67 | - End() 68 | - Filter...() 69 | - Has...() 70 | - Intersection(), which is an alias of FilterSelection() 71 | - Not...() 72 | 73 | * iteration.go : methods to loop over the selection's nodes. 74 | - Each() 75 | - EachWithBreak() 76 | - Map() 77 | 78 | * manipulation.go : methods for modifying the document 79 | - After...() 80 | - Append...() 81 | - Before...() 82 | - Clone() 83 | - Empty() 84 | - Prepend...() 85 | - Remove...() 86 | - ReplaceWith...() 87 | - Unwrap() 88 | - Wrap...() 89 | - WrapAll...() 90 | - WrapInner...() 91 | 92 | * property.go : methods that inspect and get the node's properties values. 93 | - Attr*(), RemoveAttr(), SetAttr() 94 | - AddClass(), HasClass(), RemoveClass(), ToggleClass() 95 | - Html() 96 | - Length() 97 | - Size(), which is an alias for Length() 98 | - Text() 99 | 100 | * query.go : methods that query, or reflect, a node's identity. 101 | - Contains() 102 | - Is...() 103 | 104 | * traversal.go : methods to traverse the HTML document tree. 105 | - Children...() 106 | - Contents() 107 | - Find...() 108 | - Next...() 109 | - Parent[s]...() 110 | - Prev...() 111 | - Siblings...() 112 | 113 | * type.go : definition of the types exposed by goquery. 114 | - Document 115 | - Selection 116 | - Matcher 117 | 118 | * utilities.go : definition of helper functions (and not methods on a *Selection) 119 | that are not part of jQuery, but are useful to goquery. 120 | - NodeName 121 | - OuterHtml 122 | */ 123 | package goquery 124 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/utilities.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "bytes" 5 | 6 | "golang.org/x/net/html" 7 | ) 8 | 9 | // used to determine if a set (map[*html.Node]bool) should be used 10 | // instead of iterating over a slice. The set uses more memory and 11 | // is slower than slice iteration for small N. 12 | const minNodesForSet = 1000 13 | 14 | var nodeNames = []string{ 15 | html.ErrorNode: "#error", 16 | html.TextNode: "#text", 17 | html.DocumentNode: "#document", 18 | html.CommentNode: "#comment", 19 | } 20 | 21 | // NodeName returns the node name of the first element in the selection. 22 | // It tries to behave in a similar way as the DOM's nodeName property 23 | // (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName). 24 | // 25 | // Go's net/html package defines the following node types, listed with 26 | // the corresponding returned value from this function: 27 | // 28 | // ErrorNode : #error 29 | // TextNode : #text 30 | // DocumentNode : #document 31 | // ElementNode : the element's tag name 32 | // CommentNode : #comment 33 | // DoctypeNode : the name of the document type 34 | // 35 | func NodeName(s *Selection) string { 36 | if s.Length() == 0 { 37 | return "" 38 | } 39 | switch n := s.Get(0); n.Type { 40 | case html.ElementNode, html.DoctypeNode: 41 | return n.Data 42 | default: 43 | if n.Type >= 0 && int(n.Type) < len(nodeNames) { 44 | return nodeNames[n.Type] 45 | } 46 | return "" 47 | } 48 | } 49 | 50 | // OuterHtml returns the outer HTML rendering of the first item in 51 | // the selection - that is, the HTML including the first element's 52 | // tag and attributes. 53 | // 54 | // Unlike InnerHtml, this is a function and not a method on the Selection, 55 | // because this is not a jQuery method (in javascript-land, this is 56 | // a property provided by the DOM). 57 | func OuterHtml(s *Selection) (string, error) { 58 | var buf bytes.Buffer 59 | 60 | if s.Length() == 0 { 61 | return "", nil 62 | } 63 | n := s.Get(0) 64 | if err := html.Render(&buf, n); err != nil { 65 | return "", err 66 | } 67 | return buf.String(), nil 68 | } 69 | 70 | // Loop through all container nodes to search for the target node. 71 | func sliceContains(container []*html.Node, contained *html.Node) bool { 72 | for _, n := range container { 73 | if nodeContains(n, contained) { 74 | return true 75 | } 76 | } 77 | 78 | return false 79 | } 80 | 81 | // Checks if the contained node is within the container node. 82 | func nodeContains(container *html.Node, contained *html.Node) bool { 83 | // Check if the parent of the contained node is the container node, traversing 84 | // upward until the top is reached, or the container is found. 85 | for contained = contained.Parent; contained != nil; contained = contained.Parent { 86 | if container == contained { 87 | return true 88 | } 89 | } 90 | return false 91 | } 92 | 93 | // Checks if the target node is in the slice of nodes. 94 | func isInSlice(slice []*html.Node, node *html.Node) bool { 95 | return indexInSlice(slice, node) > -1 96 | } 97 | 98 | // Returns the index of the target node in the slice, or -1. 99 | func indexInSlice(slice []*html.Node, node *html.Node) int { 100 | if node != nil { 101 | for i, n := range slice { 102 | if n == node { 103 | return i 104 | } 105 | } 106 | } 107 | return -1 108 | } 109 | 110 | // Appends the new nodes to the target slice, making sure no duplicate is added. 111 | // There is no check to the original state of the target slice, so it may still 112 | // contain duplicates. The target slice is returned because append() may create 113 | // a new underlying array. If targetSet is nil, a local set is created with the 114 | // target if len(target) + len(nodes) is greater than minNodesForSet. 115 | func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node { 116 | // if there are not that many nodes, don't use the map, faster to just use nested loops 117 | // (unless a non-nil targetSet is passed, in which case the caller knows better). 118 | if targetSet == nil && len(target)+len(nodes) < minNodesForSet { 119 | for _, n := range nodes { 120 | if !isInSlice(target, n) { 121 | target = append(target, n) 122 | } 123 | } 124 | return target 125 | } 126 | 127 | // if a targetSet is passed, then assume it is reliable, otherwise create one 128 | // and initialize it with the current target contents. 129 | if targetSet == nil { 130 | targetSet = make(map[*html.Node]bool, len(target)) 131 | for _, n := range target { 132 | targetSet[n] = true 133 | } 134 | } 135 | for _, n := range nodes { 136 | if !targetSet[n] { 137 | target = append(target, n) 138 | targetSet[n] = true 139 | } 140 | } 141 | 142 | return target 143 | } 144 | 145 | // Loop through a selection, returning only those nodes that pass the predicate 146 | // function. 147 | func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) { 148 | for i, n := range sel.Nodes { 149 | if predicate(i, newSingleSelection(n, sel.document)) { 150 | result = append(result, n) 151 | } 152 | } 153 | return result 154 | } 155 | 156 | // Creates a new Selection object based on the specified nodes, and keeps the 157 | // source Selection object on the stack (linked list). 158 | func pushStack(fromSel *Selection, nodes []*html.Node) *Selection { 159 | result := &Selection{nodes, fromSel.document, fromSel} 160 | return result 161 | } 162 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/doctype.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package html 6 | 7 | import ( 8 | "strings" 9 | ) 10 | 11 | // parseDoctype parses the data from a DoctypeToken into a name, 12 | // public identifier, and system identifier. It returns a Node whose Type 13 | // is DoctypeNode, whose Data is the name, and which has attributes 14 | // named "system" and "public" for the two identifiers if they were present. 15 | // quirks is whether the document should be parsed in "quirks mode". 16 | func parseDoctype(s string) (n *Node, quirks bool) { 17 | n = &Node{Type: DoctypeNode} 18 | 19 | // Find the name. 20 | space := strings.IndexAny(s, whitespace) 21 | if space == -1 { 22 | space = len(s) 23 | } 24 | n.Data = s[:space] 25 | // The comparison to "html" is case-sensitive. 26 | if n.Data != "html" { 27 | quirks = true 28 | } 29 | n.Data = strings.ToLower(n.Data) 30 | s = strings.TrimLeft(s[space:], whitespace) 31 | 32 | if len(s) < 6 { 33 | // It can't start with "PUBLIC" or "SYSTEM". 34 | // Ignore the rest of the string. 35 | return n, quirks || s != "" 36 | } 37 | 38 | key := strings.ToLower(s[:6]) 39 | s = s[6:] 40 | for key == "public" || key == "system" { 41 | s = strings.TrimLeft(s, whitespace) 42 | if s == "" { 43 | break 44 | } 45 | quote := s[0] 46 | if quote != '"' && quote != '\'' { 47 | break 48 | } 49 | s = s[1:] 50 | q := strings.IndexRune(s, rune(quote)) 51 | var id string 52 | if q == -1 { 53 | id = s 54 | s = "" 55 | } else { 56 | id = s[:q] 57 | s = s[q+1:] 58 | } 59 | n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) 60 | if key == "public" { 61 | key = "system" 62 | } else { 63 | key = "" 64 | } 65 | } 66 | 67 | if key != "" || s != "" { 68 | quirks = true 69 | } else if len(n.Attr) > 0 { 70 | if n.Attr[0].Key == "public" { 71 | public := strings.ToLower(n.Attr[0].Val) 72 | switch public { 73 | case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html": 74 | quirks = true 75 | default: 76 | for _, q := range quirkyIDs { 77 | if strings.HasPrefix(public, q) { 78 | quirks = true 79 | break 80 | } 81 | } 82 | } 83 | // The following two public IDs only cause quirks mode if there is no system ID. 84 | if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") || 85 | strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) { 86 | quirks = true 87 | } 88 | } 89 | if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && 90 | strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { 91 | quirks = true 92 | } 93 | } 94 | 95 | return n, quirks 96 | } 97 | 98 | // quirkyIDs is a list of public doctype identifiers that cause a document 99 | // to be interpreted in quirks mode. The identifiers should be in lower case. 100 | var quirkyIDs = []string{ 101 | "+//silmaril//dtd html pro v0r11 19970101//", 102 | "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", 103 | "-//as//dtd html 3.0 aswedit + extensions//", 104 | "-//ietf//dtd html 2.0 level 1//", 105 | "-//ietf//dtd html 2.0 level 2//", 106 | "-//ietf//dtd html 2.0 strict level 1//", 107 | "-//ietf//dtd html 2.0 strict level 2//", 108 | "-//ietf//dtd html 2.0 strict//", 109 | "-//ietf//dtd html 2.0//", 110 | "-//ietf//dtd html 2.1e//", 111 | "-//ietf//dtd html 3.0//", 112 | "-//ietf//dtd html 3.2 final//", 113 | "-//ietf//dtd html 3.2//", 114 | "-//ietf//dtd html 3//", 115 | "-//ietf//dtd html level 0//", 116 | "-//ietf//dtd html level 1//", 117 | "-//ietf//dtd html level 2//", 118 | "-//ietf//dtd html level 3//", 119 | "-//ietf//dtd html strict level 0//", 120 | "-//ietf//dtd html strict level 1//", 121 | "-//ietf//dtd html strict level 2//", 122 | "-//ietf//dtd html strict level 3//", 123 | "-//ietf//dtd html strict//", 124 | "-//ietf//dtd html//", 125 | "-//metrius//dtd metrius presentational//", 126 | "-//microsoft//dtd internet explorer 2.0 html strict//", 127 | "-//microsoft//dtd internet explorer 2.0 html//", 128 | "-//microsoft//dtd internet explorer 2.0 tables//", 129 | "-//microsoft//dtd internet explorer 3.0 html strict//", 130 | "-//microsoft//dtd internet explorer 3.0 html//", 131 | "-//microsoft//dtd internet explorer 3.0 tables//", 132 | "-//netscape comm. corp.//dtd html//", 133 | "-//netscape comm. corp.//dtd strict html//", 134 | "-//o'reilly and associates//dtd html 2.0//", 135 | "-//o'reilly and associates//dtd html extended 1.0//", 136 | "-//o'reilly and associates//dtd html extended relaxed 1.0//", 137 | "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", 138 | "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", 139 | "-//spyglass//dtd html 2.0 extended//", 140 | "-//sq//dtd html 2.0 hotmetal + extensions//", 141 | "-//sun microsystems corp.//dtd hotjava html//", 142 | "-//sun microsystems corp.//dtd hotjava strict html//", 143 | "-//w3c//dtd html 3 1995-03-24//", 144 | "-//w3c//dtd html 3.2 draft//", 145 | "-//w3c//dtd html 3.2 final//", 146 | "-//w3c//dtd html 3.2//", 147 | "-//w3c//dtd html 3.2s draft//", 148 | "-//w3c//dtd html 4.0 frameset//", 149 | "-//w3c//dtd html 4.0 transitional//", 150 | "-//w3c//dtd html experimental 19960712//", 151 | "-//w3c//dtd html experimental 970421//", 152 | "-//w3c//dtd w3 html//", 153 | "-//w3o//dtd w3 html 3.0//", 154 | "-//webtechs//dtd mozilla html 2.0//", 155 | "-//webtechs//dtd mozilla html//", 156 | } 157 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/node.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package html 6 | 7 | import ( 8 | "golang.org/x/net/html/atom" 9 | ) 10 | 11 | // A NodeType is the type of a Node. 12 | type NodeType uint32 13 | 14 | const ( 15 | ErrorNode NodeType = iota 16 | TextNode 17 | DocumentNode 18 | ElementNode 19 | CommentNode 20 | DoctypeNode 21 | scopeMarkerNode 22 | ) 23 | 24 | // Section 12.2.3.3 says "scope markers are inserted when entering applet 25 | // elements, buttons, object elements, marquees, table cells, and table 26 | // captions, and are used to prevent formatting from 'leaking'". 27 | var scopeMarker = Node{Type: scopeMarkerNode} 28 | 29 | // A Node consists of a NodeType and some Data (tag name for element nodes, 30 | // content for text) and are part of a tree of Nodes. Element nodes may also 31 | // have a Namespace and contain a slice of Attributes. Data is unescaped, so 32 | // that it looks like "a 0 { 160 | return (*s)[i-1] 161 | } 162 | return nil 163 | } 164 | 165 | // index returns the index of the top-most occurrence of n in the stack, or -1 166 | // if n is not present. 167 | func (s *nodeStack) index(n *Node) int { 168 | for i := len(*s) - 1; i >= 0; i-- { 169 | if (*s)[i] == n { 170 | return i 171 | } 172 | } 173 | return -1 174 | } 175 | 176 | // insert inserts a node at the given index. 177 | func (s *nodeStack) insert(i int, n *Node) { 178 | (*s) = append(*s, nil) 179 | copy((*s)[i+1:], (*s)[i:]) 180 | (*s)[i] = n 181 | } 182 | 183 | // remove removes a node from the stack. It is a no-op if n is not present. 184 | func (s *nodeStack) remove(n *Node) { 185 | i := s.index(n) 186 | if i == -1 { 187 | return 188 | } 189 | copy((*s)[i:], (*s)[i+1:]) 190 | j := len(*s) - 1 191 | (*s)[j] = nil 192 | *s = (*s)[:j] 193 | } 194 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/filter.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import "golang.org/x/net/html" 4 | 5 | // Filter reduces the set of matched elements to those that match the selector string. 6 | // It returns a new Selection object for this subset of matching elements. 7 | func (s *Selection) Filter(selector string) *Selection { 8 | return s.FilterMatcher(compileMatcher(selector)) 9 | } 10 | 11 | // FilterMatcher reduces the set of matched elements to those that match 12 | // the given matcher. It returns a new Selection object for this subset 13 | // of matching elements. 14 | func (s *Selection) FilterMatcher(m Matcher) *Selection { 15 | return pushStack(s, winnow(s, m, true)) 16 | } 17 | 18 | // Not removes elements from the Selection that match the selector string. 19 | // It returns a new Selection object with the matching elements removed. 20 | func (s *Selection) Not(selector string) *Selection { 21 | return s.NotMatcher(compileMatcher(selector)) 22 | } 23 | 24 | // NotMatcher removes elements from the Selection that match the given matcher. 25 | // It returns a new Selection object with the matching elements removed. 26 | func (s *Selection) NotMatcher(m Matcher) *Selection { 27 | return pushStack(s, winnow(s, m, false)) 28 | } 29 | 30 | // FilterFunction reduces the set of matched elements to those that pass the function's test. 31 | // It returns a new Selection object for this subset of elements. 32 | func (s *Selection) FilterFunction(f func(int, *Selection) bool) *Selection { 33 | return pushStack(s, winnowFunction(s, f, true)) 34 | } 35 | 36 | // NotFunction removes elements from the Selection that pass the function's test. 37 | // It returns a new Selection object with the matching elements removed. 38 | func (s *Selection) NotFunction(f func(int, *Selection) bool) *Selection { 39 | return pushStack(s, winnowFunction(s, f, false)) 40 | } 41 | 42 | // FilterNodes reduces the set of matched elements to those that match the specified nodes. 43 | // It returns a new Selection object for this subset of elements. 44 | func (s *Selection) FilterNodes(nodes ...*html.Node) *Selection { 45 | return pushStack(s, winnowNodes(s, nodes, true)) 46 | } 47 | 48 | // NotNodes removes elements from the Selection that match the specified nodes. 49 | // It returns a new Selection object with the matching elements removed. 50 | func (s *Selection) NotNodes(nodes ...*html.Node) *Selection { 51 | return pushStack(s, winnowNodes(s, nodes, false)) 52 | } 53 | 54 | // FilterSelection reduces the set of matched elements to those that match a 55 | // node in the specified Selection object. 56 | // It returns a new Selection object for this subset of elements. 57 | func (s *Selection) FilterSelection(sel *Selection) *Selection { 58 | if sel == nil { 59 | return pushStack(s, winnowNodes(s, nil, true)) 60 | } 61 | return pushStack(s, winnowNodes(s, sel.Nodes, true)) 62 | } 63 | 64 | // NotSelection removes elements from the Selection that match a node in the specified 65 | // Selection object. It returns a new Selection object with the matching elements removed. 66 | func (s *Selection) NotSelection(sel *Selection) *Selection { 67 | if sel == nil { 68 | return pushStack(s, winnowNodes(s, nil, false)) 69 | } 70 | return pushStack(s, winnowNodes(s, sel.Nodes, false)) 71 | } 72 | 73 | // Intersection is an alias for FilterSelection. 74 | func (s *Selection) Intersection(sel *Selection) *Selection { 75 | return s.FilterSelection(sel) 76 | } 77 | 78 | // Has reduces the set of matched elements to those that have a descendant 79 | // that matches the selector. 80 | // It returns a new Selection object with the matching elements. 81 | func (s *Selection) Has(selector string) *Selection { 82 | return s.HasSelection(s.document.Find(selector)) 83 | } 84 | 85 | // HasMatcher reduces the set of matched elements to those that have a descendant 86 | // that matches the matcher. 87 | // It returns a new Selection object with the matching elements. 88 | func (s *Selection) HasMatcher(m Matcher) *Selection { 89 | return s.HasSelection(s.document.FindMatcher(m)) 90 | } 91 | 92 | // HasNodes reduces the set of matched elements to those that have a 93 | // descendant that matches one of the nodes. 94 | // It returns a new Selection object with the matching elements. 95 | func (s *Selection) HasNodes(nodes ...*html.Node) *Selection { 96 | return s.FilterFunction(func(_ int, sel *Selection) bool { 97 | // Add all nodes that contain one of the specified nodes 98 | for _, n := range nodes { 99 | if sel.Contains(n) { 100 | return true 101 | } 102 | } 103 | return false 104 | }) 105 | } 106 | 107 | // HasSelection reduces the set of matched elements to those that have a 108 | // descendant that matches one of the nodes of the specified Selection object. 109 | // It returns a new Selection object with the matching elements. 110 | func (s *Selection) HasSelection(sel *Selection) *Selection { 111 | if sel == nil { 112 | return s.HasNodes() 113 | } 114 | return s.HasNodes(sel.Nodes...) 115 | } 116 | 117 | // End ends the most recent filtering operation in the current chain and 118 | // returns the set of matched elements to its previous state. 119 | func (s *Selection) End() *Selection { 120 | if s.prevSel != nil { 121 | return s.prevSel 122 | } 123 | return newEmptySelection(s.document) 124 | } 125 | 126 | // Filter based on the matcher, and the indicator to keep (Filter) or 127 | // to get rid of (Not) the matching elements. 128 | func winnow(sel *Selection, m Matcher, keep bool) []*html.Node { 129 | // Optimize if keep is requested 130 | if keep { 131 | return m.Filter(sel.Nodes) 132 | } 133 | // Use grep 134 | return grep(sel, func(i int, s *Selection) bool { 135 | return !m.Match(s.Get(0)) 136 | }) 137 | } 138 | 139 | // Filter based on an array of nodes, and the indicator to keep (Filter) or 140 | // to get rid of (Not) the matching elements. 141 | func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node { 142 | if len(nodes)+len(sel.Nodes) < minNodesForSet { 143 | return grep(sel, func(i int, s *Selection) bool { 144 | return isInSlice(nodes, s.Get(0)) == keep 145 | }) 146 | } 147 | 148 | set := make(map[*html.Node]bool) 149 | for _, n := range nodes { 150 | set[n] = true 151 | } 152 | return grep(sel, func(i int, s *Selection) bool { 153 | return set[s.Get(0)] == keep 154 | }) 155 | } 156 | 157 | // Filter based on a function test, and the indicator to keep (Filter) or 158 | // to get rid of (Not) the matching elements. 159 | func winnowFunction(sel *Selection, f func(int, *Selection) bool, keep bool) []*html.Node { 160 | return grep(sel, func(i int, s *Selection) bool { 161 | return f(i, s) == keep 162 | }) 163 | } 164 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/escape.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package html 6 | 7 | import ( 8 | "bytes" 9 | "strings" 10 | "unicode/utf8" 11 | ) 12 | 13 | // These replacements permit compatibility with old numeric entities that 14 | // assumed Windows-1252 encoding. 15 | // https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference 16 | var replacementTable = [...]rune{ 17 | '\u20AC', // First entry is what 0x80 should be replaced with. 18 | '\u0081', 19 | '\u201A', 20 | '\u0192', 21 | '\u201E', 22 | '\u2026', 23 | '\u2020', 24 | '\u2021', 25 | '\u02C6', 26 | '\u2030', 27 | '\u0160', 28 | '\u2039', 29 | '\u0152', 30 | '\u008D', 31 | '\u017D', 32 | '\u008F', 33 | '\u0090', 34 | '\u2018', 35 | '\u2019', 36 | '\u201C', 37 | '\u201D', 38 | '\u2022', 39 | '\u2013', 40 | '\u2014', 41 | '\u02DC', 42 | '\u2122', 43 | '\u0161', 44 | '\u203A', 45 | '\u0153', 46 | '\u009D', 47 | '\u017E', 48 | '\u0178', // Last entry is 0x9F. 49 | // 0x00->'\uFFFD' is handled programmatically. 50 | // 0x0D->'\u000D' is a no-op. 51 | } 52 | 53 | // unescapeEntity reads an entity like "<" from b[src:] and writes the 54 | // corresponding "<" to b[dst:], returning the incremented dst and src cursors. 55 | // Precondition: b[src] == '&' && dst <= src. 56 | // attribute should be true if parsing an attribute value. 57 | func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) { 58 | // https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference 59 | 60 | // i starts at 1 because we already know that s[0] == '&'. 61 | i, s := 1, b[src:] 62 | 63 | if len(s) <= 1 { 64 | b[dst] = b[src] 65 | return dst + 1, src + 1 66 | } 67 | 68 | if s[i] == '#' { 69 | if len(s) <= 3 { // We need to have at least "&#.". 70 | b[dst] = b[src] 71 | return dst + 1, src + 1 72 | } 73 | i++ 74 | c := s[i] 75 | hex := false 76 | if c == 'x' || c == 'X' { 77 | hex = true 78 | i++ 79 | } 80 | 81 | x := '\x00' 82 | for i < len(s) { 83 | c = s[i] 84 | i++ 85 | if hex { 86 | if '0' <= c && c <= '9' { 87 | x = 16*x + rune(c) - '0' 88 | continue 89 | } else if 'a' <= c && c <= 'f' { 90 | x = 16*x + rune(c) - 'a' + 10 91 | continue 92 | } else if 'A' <= c && c <= 'F' { 93 | x = 16*x + rune(c) - 'A' + 10 94 | continue 95 | } 96 | } else if '0' <= c && c <= '9' { 97 | x = 10*x + rune(c) - '0' 98 | continue 99 | } 100 | if c != ';' { 101 | i-- 102 | } 103 | break 104 | } 105 | 106 | if i <= 3 { // No characters matched. 107 | b[dst] = b[src] 108 | return dst + 1, src + 1 109 | } 110 | 111 | if 0x80 <= x && x <= 0x9F { 112 | // Replace characters from Windows-1252 with UTF-8 equivalents. 113 | x = replacementTable[x-0x80] 114 | } else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF { 115 | // Replace invalid characters with the replacement character. 116 | x = '\uFFFD' 117 | } 118 | 119 | return dst + utf8.EncodeRune(b[dst:], x), src + i 120 | } 121 | 122 | // Consume the maximum number of characters possible, with the 123 | // consumed characters matching one of the named references. 124 | 125 | for i < len(s) { 126 | c := s[i] 127 | i++ 128 | // Lower-cased characters are more common in entities, so we check for them first. 129 | if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { 130 | continue 131 | } 132 | if c != ';' { 133 | i-- 134 | } 135 | break 136 | } 137 | 138 | entityName := string(s[1:i]) 139 | if entityName == "" { 140 | // No-op. 141 | } else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' { 142 | // No-op. 143 | } else if x := entity[entityName]; x != 0 { 144 | return dst + utf8.EncodeRune(b[dst:], x), src + i 145 | } else if x := entity2[entityName]; x[0] != 0 { 146 | dst1 := dst + utf8.EncodeRune(b[dst:], x[0]) 147 | return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i 148 | } else if !attribute { 149 | maxLen := len(entityName) - 1 150 | if maxLen > longestEntityWithoutSemicolon { 151 | maxLen = longestEntityWithoutSemicolon 152 | } 153 | for j := maxLen; j > 1; j-- { 154 | if x := entity[entityName[:j]]; x != 0 { 155 | return dst + utf8.EncodeRune(b[dst:], x), src + j + 1 156 | } 157 | } 158 | } 159 | 160 | dst1, src1 = dst+i, src+i 161 | copy(b[dst:dst1], b[src:src1]) 162 | return dst1, src1 163 | } 164 | 165 | // unescape unescapes b's entities in-place, so that "a<b" becomes "a': 214 | esc = ">" 215 | case '"': 216 | // """ is shorter than """. 217 | esc = """ 218 | case '\r': 219 | esc = " " 220 | default: 221 | panic("unrecognized escape character") 222 | } 223 | s = s[i+1:] 224 | if _, err := w.WriteString(esc); err != nil { 225 | return err 226 | } 227 | i = strings.IndexAny(s, escapedChars) 228 | } 229 | _, err := w.WriteString(s) 230 | return err 231 | } 232 | 233 | // EscapeString escapes special characters like "<" to become "<". It 234 | // escapes only five such characters: <, >, &, ' and ". 235 | // UnescapeString(EscapeString(s)) == s always holds, but the converse isn't 236 | // always true. 237 | func EscapeString(s string) string { 238 | if strings.IndexAny(s, escapedChars) == -1 { 239 | return s 240 | } 241 | var buf bytes.Buffer 242 | escape(&buf, s) 243 | return buf.String() 244 | } 245 | 246 | // UnescapeString unescapes entities like "<" to become "<". It unescapes a 247 | // larger range of entities than EscapeString escapes. For example, "á" 248 | // unescapes to "á", as does "á" and "&xE1;". 249 | // UnescapeString(EscapeString(s)) == s always holds, but the converse isn't 250 | // always true. 251 | func UnescapeString(s string) string { 252 | for _, c := range s { 253 | if c == '&' { 254 | return string(unescape([]byte(s), false)) 255 | } 256 | } 257 | return s 258 | } 259 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/property.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "bytes" 5 | "regexp" 6 | "strings" 7 | 8 | "golang.org/x/net/html" 9 | ) 10 | 11 | var rxClassTrim = regexp.MustCompile("[\t\r\n]") 12 | 13 | // Attr gets the specified attribute's value for the first element in the 14 | // Selection. To get the value for each element individually, use a looping 15 | // construct such as Each or Map method. 16 | func (s *Selection) Attr(attrName string) (val string, exists bool) { 17 | if len(s.Nodes) == 0 { 18 | return 19 | } 20 | return getAttributeValue(attrName, s.Nodes[0]) 21 | } 22 | 23 | // AttrOr works like Attr but returns default value if attribute is not present. 24 | func (s *Selection) AttrOr(attrName, defaultValue string) string { 25 | if len(s.Nodes) == 0 { 26 | return defaultValue 27 | } 28 | 29 | val, exists := getAttributeValue(attrName, s.Nodes[0]) 30 | if !exists { 31 | return defaultValue 32 | } 33 | 34 | return val 35 | } 36 | 37 | // RemoveAttr removes the named attribute from each element in the set of matched elements. 38 | func (s *Selection) RemoveAttr(attrName string) *Selection { 39 | for _, n := range s.Nodes { 40 | removeAttr(n, attrName) 41 | } 42 | 43 | return s 44 | } 45 | 46 | // SetAttr sets the given attribute on each element in the set of matched elements. 47 | func (s *Selection) SetAttr(attrName, val string) *Selection { 48 | for _, n := range s.Nodes { 49 | attr := getAttributePtr(attrName, n) 50 | if attr == nil { 51 | n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val}) 52 | } else { 53 | attr.Val = val 54 | } 55 | } 56 | 57 | return s 58 | } 59 | 60 | // Text gets the combined text contents of each element in the set of matched 61 | // elements, including their descendants. 62 | func (s *Selection) Text() string { 63 | var buf bytes.Buffer 64 | 65 | // Slightly optimized vs calling Each: no single selection object created 66 | var f func(*html.Node) 67 | f = func(n *html.Node) { 68 | if n.Type == html.TextNode { 69 | // Keep newlines and spaces, like jQuery 70 | buf.WriteString(n.Data) 71 | } 72 | if n.FirstChild != nil { 73 | for c := n.FirstChild; c != nil; c = c.NextSibling { 74 | f(c) 75 | } 76 | } 77 | } 78 | for _, n := range s.Nodes { 79 | f(n) 80 | } 81 | 82 | return buf.String() 83 | } 84 | 85 | // Size is an alias for Length. 86 | func (s *Selection) Size() int { 87 | return s.Length() 88 | } 89 | 90 | // Length returns the number of elements in the Selection object. 91 | func (s *Selection) Length() int { 92 | return len(s.Nodes) 93 | } 94 | 95 | // Html gets the HTML contents of the first element in the set of matched 96 | // elements. It includes text and comment nodes. 97 | func (s *Selection) Html() (ret string, e error) { 98 | // Since there is no .innerHtml, the HTML content must be re-created from 99 | // the nodes using html.Render. 100 | var buf bytes.Buffer 101 | 102 | if len(s.Nodes) > 0 { 103 | for c := s.Nodes[0].FirstChild; c != nil; c = c.NextSibling { 104 | e = html.Render(&buf, c) 105 | if e != nil { 106 | return 107 | } 108 | } 109 | ret = buf.String() 110 | } 111 | 112 | return 113 | } 114 | 115 | // AddClass adds the given class(es) to each element in the set of matched elements. 116 | // Multiple class names can be specified, separated by a space or via multiple arguments. 117 | func (s *Selection) AddClass(class ...string) *Selection { 118 | classStr := strings.TrimSpace(strings.Join(class, " ")) 119 | 120 | if classStr == "" { 121 | return s 122 | } 123 | 124 | tcls := getClassesSlice(classStr) 125 | for _, n := range s.Nodes { 126 | curClasses, attr := getClassesAndAttr(n, true) 127 | for _, newClass := range tcls { 128 | if !strings.Contains(curClasses, " "+newClass+" ") { 129 | curClasses += newClass + " " 130 | } 131 | } 132 | 133 | setClasses(n, attr, curClasses) 134 | } 135 | 136 | return s 137 | } 138 | 139 | // HasClass determines whether any of the matched elements are assigned the 140 | // given class. 141 | func (s *Selection) HasClass(class string) bool { 142 | class = " " + class + " " 143 | for _, n := range s.Nodes { 144 | classes, _ := getClassesAndAttr(n, false) 145 | if strings.Contains(classes, class) { 146 | return true 147 | } 148 | } 149 | return false 150 | } 151 | 152 | // RemoveClass removes the given class(es) from each element in the set of matched elements. 153 | // Multiple class names can be specified, separated by a space or via multiple arguments. 154 | // If no class name is provided, all classes are removed. 155 | func (s *Selection) RemoveClass(class ...string) *Selection { 156 | var rclasses []string 157 | 158 | classStr := strings.TrimSpace(strings.Join(class, " ")) 159 | remove := classStr == "" 160 | 161 | if !remove { 162 | rclasses = getClassesSlice(classStr) 163 | } 164 | 165 | for _, n := range s.Nodes { 166 | if remove { 167 | removeAttr(n, "class") 168 | } else { 169 | classes, attr := getClassesAndAttr(n, true) 170 | for _, rcl := range rclasses { 171 | classes = strings.Replace(classes, " "+rcl+" ", " ", -1) 172 | } 173 | 174 | setClasses(n, attr, classes) 175 | } 176 | } 177 | 178 | return s 179 | } 180 | 181 | // ToggleClass adds or removes the given class(es) for each element in the set of matched elements. 182 | // Multiple class names can be specified, separated by a space or via multiple arguments. 183 | func (s *Selection) ToggleClass(class ...string) *Selection { 184 | classStr := strings.TrimSpace(strings.Join(class, " ")) 185 | 186 | if classStr == "" { 187 | return s 188 | } 189 | 190 | tcls := getClassesSlice(classStr) 191 | 192 | for _, n := range s.Nodes { 193 | classes, attr := getClassesAndAttr(n, true) 194 | for _, tcl := range tcls { 195 | if strings.Contains(classes, " "+tcl+" ") { 196 | classes = strings.Replace(classes, " "+tcl+" ", " ", -1) 197 | } else { 198 | classes += tcl + " " 199 | } 200 | } 201 | 202 | setClasses(n, attr, classes) 203 | } 204 | 205 | return s 206 | } 207 | 208 | func getAttributePtr(attrName string, n *html.Node) *html.Attribute { 209 | if n == nil { 210 | return nil 211 | } 212 | 213 | for i, a := range n.Attr { 214 | if a.Key == attrName { 215 | return &n.Attr[i] 216 | } 217 | } 218 | return nil 219 | } 220 | 221 | // Private function to get the specified attribute's value from a node. 222 | func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) { 223 | if a := getAttributePtr(attrName, n); a != nil { 224 | val = a.Val 225 | exists = true 226 | } 227 | return 228 | } 229 | 230 | // Get and normalize the "class" attribute from the node. 231 | func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) { 232 | // Applies only to element nodes 233 | if n.Type == html.ElementNode { 234 | attr = getAttributePtr("class", n) 235 | if attr == nil && create { 236 | n.Attr = append(n.Attr, html.Attribute{ 237 | Key: "class", 238 | Val: "", 239 | }) 240 | attr = &n.Attr[len(n.Attr)-1] 241 | } 242 | } 243 | 244 | if attr == nil { 245 | classes = " " 246 | } else { 247 | classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ") 248 | } 249 | 250 | return 251 | } 252 | 253 | func getClassesSlice(classes string) []string { 254 | return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ") 255 | } 256 | 257 | func removeAttr(n *html.Node, attrName string) { 258 | for i, a := range n.Attr { 259 | if a.Key == attrName { 260 | n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr = 261 | n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1] 262 | return 263 | } 264 | } 265 | } 266 | 267 | func setClasses(n *html.Node, attr *html.Attribute, classes string) { 268 | classes = strings.TrimSpace(classes) 269 | if classes == "" { 270 | removeAttr(n, "class") 271 | return 272 | } 273 | 274 | attr.Val = classes 275 | } 276 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/foreign.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package html 6 | 7 | import ( 8 | "strings" 9 | ) 10 | 11 | func adjustAttributeNames(aa []Attribute, nameMap map[string]string) { 12 | for i := range aa { 13 | if newName, ok := nameMap[aa[i].Key]; ok { 14 | aa[i].Key = newName 15 | } 16 | } 17 | } 18 | 19 | func adjustForeignAttributes(aa []Attribute) { 20 | for i, a := range aa { 21 | if a.Key == "" || a.Key[0] != 'x' { 22 | continue 23 | } 24 | switch a.Key { 25 | case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show", 26 | "xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink": 27 | j := strings.Index(a.Key, ":") 28 | aa[i].Namespace = a.Key[:j] 29 | aa[i].Key = a.Key[j+1:] 30 | } 31 | } 32 | } 33 | 34 | func htmlIntegrationPoint(n *Node) bool { 35 | if n.Type != ElementNode { 36 | return false 37 | } 38 | switch n.Namespace { 39 | case "math": 40 | if n.Data == "annotation-xml" { 41 | for _, a := range n.Attr { 42 | if a.Key == "encoding" { 43 | val := strings.ToLower(a.Val) 44 | if val == "text/html" || val == "application/xhtml+xml" { 45 | return true 46 | } 47 | } 48 | } 49 | } 50 | case "svg": 51 | switch n.Data { 52 | case "desc", "foreignObject", "title": 53 | return true 54 | } 55 | } 56 | return false 57 | } 58 | 59 | func mathMLTextIntegrationPoint(n *Node) bool { 60 | if n.Namespace != "math" { 61 | return false 62 | } 63 | switch n.Data { 64 | case "mi", "mo", "mn", "ms", "mtext": 65 | return true 66 | } 67 | return false 68 | } 69 | 70 | // Section 12.2.5.5. 71 | var breakout = map[string]bool{ 72 | "b": true, 73 | "big": true, 74 | "blockquote": true, 75 | "body": true, 76 | "br": true, 77 | "center": true, 78 | "code": true, 79 | "dd": true, 80 | "div": true, 81 | "dl": true, 82 | "dt": true, 83 | "em": true, 84 | "embed": true, 85 | "h1": true, 86 | "h2": true, 87 | "h3": true, 88 | "h4": true, 89 | "h5": true, 90 | "h6": true, 91 | "head": true, 92 | "hr": true, 93 | "i": true, 94 | "img": true, 95 | "li": true, 96 | "listing": true, 97 | "menu": true, 98 | "meta": true, 99 | "nobr": true, 100 | "ol": true, 101 | "p": true, 102 | "pre": true, 103 | "ruby": true, 104 | "s": true, 105 | "small": true, 106 | "span": true, 107 | "strong": true, 108 | "strike": true, 109 | "sub": true, 110 | "sup": true, 111 | "table": true, 112 | "tt": true, 113 | "u": true, 114 | "ul": true, 115 | "var": true, 116 | } 117 | 118 | // Section 12.2.5.5. 119 | var svgTagNameAdjustments = map[string]string{ 120 | "altglyph": "altGlyph", 121 | "altglyphdef": "altGlyphDef", 122 | "altglyphitem": "altGlyphItem", 123 | "animatecolor": "animateColor", 124 | "animatemotion": "animateMotion", 125 | "animatetransform": "animateTransform", 126 | "clippath": "clipPath", 127 | "feblend": "feBlend", 128 | "fecolormatrix": "feColorMatrix", 129 | "fecomponenttransfer": "feComponentTransfer", 130 | "fecomposite": "feComposite", 131 | "feconvolvematrix": "feConvolveMatrix", 132 | "fediffuselighting": "feDiffuseLighting", 133 | "fedisplacementmap": "feDisplacementMap", 134 | "fedistantlight": "feDistantLight", 135 | "feflood": "feFlood", 136 | "fefunca": "feFuncA", 137 | "fefuncb": "feFuncB", 138 | "fefuncg": "feFuncG", 139 | "fefuncr": "feFuncR", 140 | "fegaussianblur": "feGaussianBlur", 141 | "feimage": "feImage", 142 | "femerge": "feMerge", 143 | "femergenode": "feMergeNode", 144 | "femorphology": "feMorphology", 145 | "feoffset": "feOffset", 146 | "fepointlight": "fePointLight", 147 | "fespecularlighting": "feSpecularLighting", 148 | "fespotlight": "feSpotLight", 149 | "fetile": "feTile", 150 | "feturbulence": "feTurbulence", 151 | "foreignobject": "foreignObject", 152 | "glyphref": "glyphRef", 153 | "lineargradient": "linearGradient", 154 | "radialgradient": "radialGradient", 155 | "textpath": "textPath", 156 | } 157 | 158 | // Section 12.2.5.1 159 | var mathMLAttributeAdjustments = map[string]string{ 160 | "definitionurl": "definitionURL", 161 | } 162 | 163 | var svgAttributeAdjustments = map[string]string{ 164 | "attributename": "attributeName", 165 | "attributetype": "attributeType", 166 | "basefrequency": "baseFrequency", 167 | "baseprofile": "baseProfile", 168 | "calcmode": "calcMode", 169 | "clippathunits": "clipPathUnits", 170 | "contentscripttype": "contentScriptType", 171 | "contentstyletype": "contentStyleType", 172 | "diffuseconstant": "diffuseConstant", 173 | "edgemode": "edgeMode", 174 | "externalresourcesrequired": "externalResourcesRequired", 175 | "filterres": "filterRes", 176 | "filterunits": "filterUnits", 177 | "glyphref": "glyphRef", 178 | "gradienttransform": "gradientTransform", 179 | "gradientunits": "gradientUnits", 180 | "kernelmatrix": "kernelMatrix", 181 | "kernelunitlength": "kernelUnitLength", 182 | "keypoints": "keyPoints", 183 | "keysplines": "keySplines", 184 | "keytimes": "keyTimes", 185 | "lengthadjust": "lengthAdjust", 186 | "limitingconeangle": "limitingConeAngle", 187 | "markerheight": "markerHeight", 188 | "markerunits": "markerUnits", 189 | "markerwidth": "markerWidth", 190 | "maskcontentunits": "maskContentUnits", 191 | "maskunits": "maskUnits", 192 | "numoctaves": "numOctaves", 193 | "pathlength": "pathLength", 194 | "patterncontentunits": "patternContentUnits", 195 | "patterntransform": "patternTransform", 196 | "patternunits": "patternUnits", 197 | "pointsatx": "pointsAtX", 198 | "pointsaty": "pointsAtY", 199 | "pointsatz": "pointsAtZ", 200 | "preservealpha": "preserveAlpha", 201 | "preserveaspectratio": "preserveAspectRatio", 202 | "primitiveunits": "primitiveUnits", 203 | "refx": "refX", 204 | "refy": "refY", 205 | "repeatcount": "repeatCount", 206 | "repeatdur": "repeatDur", 207 | "requiredextensions": "requiredExtensions", 208 | "requiredfeatures": "requiredFeatures", 209 | "specularconstant": "specularConstant", 210 | "specularexponent": "specularExponent", 211 | "spreadmethod": "spreadMethod", 212 | "startoffset": "startOffset", 213 | "stddeviation": "stdDeviation", 214 | "stitchtiles": "stitchTiles", 215 | "surfacescale": "surfaceScale", 216 | "systemlanguage": "systemLanguage", 217 | "tablevalues": "tableValues", 218 | "targetx": "targetX", 219 | "targety": "targetY", 220 | "textlength": "textLength", 221 | "viewbox": "viewBox", 222 | "viewtarget": "viewTarget", 223 | "xchannelselector": "xChannelSelector", 224 | "ychannelselector": "yChannelSelector", 225 | "zoomandpan": "zoomAndPan", 226 | } 227 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/render.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package html 6 | 7 | import ( 8 | "bufio" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "strings" 13 | ) 14 | 15 | type writer interface { 16 | io.Writer 17 | io.ByteWriter 18 | WriteString(string) (int, error) 19 | } 20 | 21 | // Render renders the parse tree n to the given writer. 22 | // 23 | // Rendering is done on a 'best effort' basis: calling Parse on the output of 24 | // Render will always result in something similar to the original tree, but it 25 | // is not necessarily an exact clone unless the original tree was 'well-formed'. 26 | // 'Well-formed' is not easily specified; the HTML5 specification is 27 | // complicated. 28 | // 29 | // Calling Parse on arbitrary input typically results in a 'well-formed' parse 30 | // tree. However, it is possible for Parse to yield a 'badly-formed' parse tree. 31 | // For example, in a 'well-formed' parse tree, no element is a child of 32 | // another element: parsing "" results in two sibling elements. 33 | // Similarly, in a 'well-formed' parse tree, no element is a child of a 34 | // element: parsing "

" results in a

with two sibling 35 | // children; the is reparented to the

's parent. However, calling 36 | // Parse on "
" does not return an error, but the result has an 37 | // element with an child, and is therefore not 'well-formed'. 38 | // 39 | // Programmatically constructed trees are typically also 'well-formed', but it 40 | // is possible to construct a tree that looks innocuous but, when rendered and 41 | // re-parsed, results in a different tree. A simple example is that a solitary 42 | // text node would become a tree containing , and elements. 43 | // Another example is that the programmatic equivalent of "abc" 44 | // becomes "abc". 45 | func Render(w io.Writer, n *Node) error { 46 | if x, ok := w.(writer); ok { 47 | return render(x, n) 48 | } 49 | buf := bufio.NewWriter(w) 50 | if err := render(buf, n); err != nil { 51 | return err 52 | } 53 | return buf.Flush() 54 | } 55 | 56 | // plaintextAbort is returned from render1 when a element 57 | // has been rendered. No more end tags should be rendered after that. 58 | var plaintextAbort = errors.New("html: internal error (plaintext abort)") 59 | 60 | func render(w writer, n *Node) error { 61 | err := render1(w, n) 62 | if err == plaintextAbort { 63 | err = nil 64 | } 65 | return err 66 | } 67 | 68 | func render1(w writer, n *Node) error { 69 | // Render non-element nodes; these are the easy cases. 70 | switch n.Type { 71 | case ErrorNode: 72 | return errors.New("html: cannot render an ErrorNode node") 73 | case TextNode: 74 | return escape(w, n.Data) 75 | case DocumentNode: 76 | for c := n.FirstChild; c != nil; c = c.NextSibling { 77 | if err := render1(w, c); err != nil { 78 | return err 79 | } 80 | } 81 | return nil 82 | case ElementNode: 83 | // No-op. 84 | case CommentNode: 85 | if _, err := w.WriteString("<!--"); err != nil { 86 | return err 87 | } 88 | if _, err := w.WriteString(n.Data); err != nil { 89 | return err 90 | } 91 | if _, err := w.WriteString("-->"); err != nil { 92 | return err 93 | } 94 | return nil 95 | case DoctypeNode: 96 | if _, err := w.WriteString("<!DOCTYPE "); err != nil { 97 | return err 98 | } 99 | if _, err := w.WriteString(n.Data); err != nil { 100 | return err 101 | } 102 | if n.Attr != nil { 103 | var p, s string 104 | for _, a := range n.Attr { 105 | switch a.Key { 106 | case "public": 107 | p = a.Val 108 | case "system": 109 | s = a.Val 110 | } 111 | } 112 | if p != "" { 113 | if _, err := w.WriteString(" PUBLIC "); err != nil { 114 | return err 115 | } 116 | if err := writeQuoted(w, p); err != nil { 117 | return err 118 | } 119 | if s != "" { 120 | if err := w.WriteByte(' '); err != nil { 121 | return err 122 | } 123 | if err := writeQuoted(w, s); err != nil { 124 | return err 125 | } 126 | } 127 | } else if s != "" { 128 | if _, err := w.WriteString(" SYSTEM "); err != nil { 129 | return err 130 | } 131 | if err := writeQuoted(w, s); err != nil { 132 | return err 133 | } 134 | } 135 | } 136 | return w.WriteByte('>') 137 | default: 138 | return errors.New("html: unknown node type") 139 | } 140 | 141 | // Render the <xxx> opening tag. 142 | if err := w.WriteByte('<'); err != nil { 143 | return err 144 | } 145 | if _, err := w.WriteString(n.Data); err != nil { 146 | return err 147 | } 148 | for _, a := range n.Attr { 149 | if err := w.WriteByte(' '); err != nil { 150 | return err 151 | } 152 | if a.Namespace != "" { 153 | if _, err := w.WriteString(a.Namespace); err != nil { 154 | return err 155 | } 156 | if err := w.WriteByte(':'); err != nil { 157 | return err 158 | } 159 | } 160 | if _, err := w.WriteString(a.Key); err != nil { 161 | return err 162 | } 163 | if _, err := w.WriteString(`="`); err != nil { 164 | return err 165 | } 166 | if err := escape(w, a.Val); err != nil { 167 | return err 168 | } 169 | if err := w.WriteByte('"'); err != nil { 170 | return err 171 | } 172 | } 173 | if voidElements[n.Data] { 174 | if n.FirstChild != nil { 175 | return fmt.Errorf("html: void element <%s> has child nodes", n.Data) 176 | } 177 | _, err := w.WriteString("/>") 178 | return err 179 | } 180 | if err := w.WriteByte('>'); err != nil { 181 | return err 182 | } 183 | 184 | // Add initial newline where there is danger of a newline beging ignored. 185 | if c := n.FirstChild; c != nil && c.Type == TextNode && strings.HasPrefix(c.Data, "\n") { 186 | switch n.Data { 187 | case "pre", "listing", "textarea": 188 | if err := w.WriteByte('\n'); err != nil { 189 | return err 190 | } 191 | } 192 | } 193 | 194 | // Render any child nodes. 195 | switch n.Data { 196 | case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": 197 | for c := n.FirstChild; c != nil; c = c.NextSibling { 198 | if c.Type == TextNode { 199 | if _, err := w.WriteString(c.Data); err != nil { 200 | return err 201 | } 202 | } else { 203 | if err := render1(w, c); err != nil { 204 | return err 205 | } 206 | } 207 | } 208 | if n.Data == "plaintext" { 209 | // Don't render anything else. <plaintext> must be the 210 | // last element in the file, with no closing tag. 211 | return plaintextAbort 212 | } 213 | default: 214 | for c := n.FirstChild; c != nil; c = c.NextSibling { 215 | if err := render1(w, c); err != nil { 216 | return err 217 | } 218 | } 219 | } 220 | 221 | // Render the </xxx> closing tag. 222 | if _, err := w.WriteString("</"); err != nil { 223 | return err 224 | } 225 | if _, err := w.WriteString(n.Data); err != nil { 226 | return err 227 | } 228 | return w.WriteByte('>') 229 | } 230 | 231 | // writeQuoted writes s to w surrounded by quotes. Normally it will use double 232 | // quotes, but if s contains a double quote, it will use single quotes. 233 | // It is used for writing the identifiers in a doctype declaration. 234 | // In valid HTML, they can't contain both types of quotes. 235 | func writeQuoted(w writer, s string) error { 236 | var q byte = '"' 237 | if strings.Contains(s, `"`) { 238 | q = '\'' 239 | } 240 | if err := w.WriteByte(q); err != nil { 241 | return err 242 | } 243 | if _, err := w.WriteString(s); err != nil { 244 | return err 245 | } 246 | if err := w.WriteByte(q); err != nil { 247 | return err 248 | } 249 | return nil 250 | } 251 | 252 | // Section 12.1.2, "Elements", gives this list of void elements. Void elements 253 | // are those that can't have any contents. 254 | var voidElements = map[string]bool{ 255 | "area": true, 256 | "base": true, 257 | "br": true, 258 | "col": true, 259 | "command": true, 260 | "embed": true, 261 | "hr": true, 262 | "img": true, 263 | "input": true, 264 | "keygen": true, 265 | "link": true, 266 | "meta": true, 267 | "param": true, 268 | "source": true, 269 | "track": true, 270 | "wbr": true, 271 | } 272 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/README.md: -------------------------------------------------------------------------------- 1 | # goquery - a little like that j-thing, only in Go [![build status](https://secure.travis-ci.org/PuerkitoBio/goquery.png)](http://travis-ci.org/PuerkitoBio/goquery) [![GoDoc](https://godoc.org/github.com/PuerkitoBio/goquery?status.png)](http://godoc.org/github.com/PuerkitoBio/goquery) 2 | 3 | goquery brings a syntax and a set of features similar to [jQuery][] to the [Go language][go]. It is based on Go's [net/html package][html] and the CSS Selector library [cascadia][]. Since the net/html parser returns nodes, and not a full-featured DOM tree, jQuery's stateful manipulation functions (like height(), css(), detach()) have been left off. 4 | 5 | Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML. See the [wiki][] for various options to do this. 6 | 7 | Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...). 8 | 9 | ## Installation 10 | 11 | Please note that because of the net/html dependency, goquery requires Go1.1+. 12 | 13 | $ go get github.com/PuerkitoBio/goquery 14 | 15 | (optional) To run unit tests: 16 | 17 | $ cd $GOPATH/src/github.com/PuerkitoBio/goquery 18 | $ go test 19 | 20 | (optional) To run benchmarks (warning: it runs for a few minutes): 21 | 22 | $ cd $GOPATH/src/github.com/PuerkitoBio/goquery 23 | $ go test -bench=".*" 24 | 25 | ## Changelog 26 | 27 | **Note that goquery's API is now stable, and will not break.** 28 | 29 | * **2017-02-12 (v1.1.0)** : Add `SetHtml` and `SetText` (thanks to @glebtv). 30 | * **2016-12-29 (v1.0.2)** : Optimize allocations for `Selection.Text` (thanks to @radovskyb). 31 | * **2016-08-28 (v1.0.1)** : Optimize performance for large documents. 32 | * **2016-07-27 (v1.0.0)** : Tag version 1.0.0. 33 | * **2016-06-15** : Invalid selector strings internally compile to a `Matcher` implementation that never matches any node (instead of a panic). So for example, `doc.Find("~")` returns an empty `*Selection` object. 34 | * **2016-02-02** : Add `NodeName` utility function similar to the DOM's `nodeName` property. It returns the tag name of the first element in a selection, and other relevant values of non-element nodes (see godoc for details). Add `OuterHtml` utility function similar to the DOM's `outerHTML` property (named `OuterHtml` in small caps for consistency with the existing `Html` method on the `Selection`). 35 | * **2015-04-20** : Add `AttrOr` helper method to return the attribute's value or a default value if absent. Thanks to [piotrkowalczuk][piotr]. 36 | * **2015-02-04** : Add more manipulation functions - Prepend* - thanks again to [Andrew Stone][thatguystone]. 37 | * **2014-11-28** : Add more manipulation functions - ReplaceWith*, Wrap* and Unwrap - thanks again to [Andrew Stone][thatguystone]. 38 | * **2014-11-07** : Add manipulation functions (thanks to [Andrew Stone][thatguystone]) and `*Matcher` functions, that receive compiled cascadia selectors instead of selector strings, thus avoiding potential panics thrown by goquery via `cascadia.MustCompile` calls. This results in better performance (selectors can be compiled once and reused) and more idiomatic error handling (you can handle cascadia's compilation errors, instead of recovering from panics, which had been bugging me for a long time). Note that the actual type expected is a `Matcher` interface, that `cascadia.Selector` implements. Other matcher implementations could be used. 39 | * **2014-11-06** : Change import paths of net/html to golang.org/x/net/html (see https://groups.google.com/forum/#!topic/golang-nuts/eD8dh3T9yyA). Make sure to update your code to use the new import path too when you call goquery with `html.Node`s. 40 | * **v0.3.2** : Add `NewDocumentFromReader()` (thanks jweir) which allows creating a goquery document from an io.Reader. 41 | * **v0.3.1** : Add `NewDocumentFromResponse()` (thanks assassingj) which allows creating a goquery document from an http response. 42 | * **v0.3.0** : Add `EachWithBreak()` which allows to break out of an `Each()` loop by returning false. This function was added instead of changing the existing `Each()` to avoid breaking compatibility. 43 | * **v0.2.1** : Make go-getable, now that [go.net/html is Go1.0-compatible][gonet] (thanks to @matrixik for pointing this out). 44 | * **v0.2.0** : Add support for negative indices in Slice(). **BREAKING CHANGE** `Document.Root` is removed, `Document` is now a `Selection` itself (a selection of one, the root element, just like `Document.Root` was before). Add jQuery's Closest() method. 45 | * **v0.1.1** : Add benchmarks to use as baseline for refactorings, refactor Next...() and Prev...() methods to use the new html package's linked list features (Next/PrevSibling, FirstChild). Good performance boost (40+% in some cases). 46 | * **v0.1.0** : Initial release. 47 | 48 | ## API 49 | 50 | goquery exposes two structs, `Document` and `Selection`, and the `Matcher` interface. Unlike jQuery, which is loaded as part of a DOM document, and thus acts on its containing document, goquery doesn't know which HTML document to act upon. So it needs to be told, and that's what the `Document` type is for. It holds the root document node as the initial Selection value to manipulate. 51 | 52 | jQuery often has many variants for the same function (no argument, a selector string argument, a jQuery object argument, a DOM element argument, ...). Instead of exposing the same features in goquery as a single method with variadic empty interface arguments, statically-typed signatures are used following this naming convention: 53 | 54 | * When the jQuery equivalent can be called with no argument, it has the same name as jQuery for the no argument signature (e.g.: `Prev()`), and the version with a selector string argument is called `XxxFiltered()` (e.g.: `PrevFiltered()`) 55 | * When the jQuery equivalent **requires** one argument, the same name as jQuery is used for the selector string version (e.g.: `Is()`) 56 | * The signatures accepting a jQuery object as argument are defined in goquery as `XxxSelection()` and take a `*Selection` object as argument (e.g.: `FilterSelection()`) 57 | * The signatures accepting a DOM element as argument in jQuery are defined in goquery as `XxxNodes()` and take a variadic argument of type `*html.Node` (e.g.: `FilterNodes()`) 58 | * The signatures accepting a function as argument in jQuery are defined in goquery as `XxxFunction()` and take a function as argument (e.g.: `FilterFunction()`) 59 | * The goquery methods that can be called with a selector string have a corresponding version that take a `Matcher` interface and are defined as `XxxMatcher()` (e.g.: `IsMatcher()`) 60 | 61 | Utility functions that are not in jQuery but are useful in Go are implemented as functions (that take a `*Selection` as parameter), to avoid a potential naming clash on the `*Selection`'s methods (reserved for jQuery-equivalent behaviour). 62 | 63 | The complete [godoc reference documentation can be found here][doc]. 64 | 65 | Please note that Cascadia's selectors do not necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details. Invalid selector strings compile to a `Matcher` that fails to match any node. Behaviour of the various functions that take a selector string as argument follows from that fact, e.g. (where `~` is an invalid selector string): 66 | 67 | * `Find("~")` returns an empty selection because the selector string doesn't match anything. 68 | * `Add("~")` returns a new selection that holds the same nodes as the original selection, because it didn't add any node (selector string didn't match anything). 69 | * `ParentsFiltered("~")` returns an empty selection because the selector string doesn't match anything. 70 | * `ParentsUntil("~")` returns all parents of the selection because the selector string didn't match any element to stop before the top element. 71 | 72 | ## Examples 73 | 74 | See some tips and tricks in the [wiki][]. 75 | 76 | Adapted from example_test.go: 77 | 78 | ```Go 79 | package main 80 | 81 | import ( 82 | "fmt" 83 | "log" 84 | 85 | "github.com/PuerkitoBio/goquery" 86 | ) 87 | 88 | func ExampleScrape() { 89 | doc, err := goquery.NewDocument("http://metalsucks.net") 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | 94 | // Find the review items 95 | doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) { 96 | // For each item found, get the band and title 97 | band := s.Find("a").Text() 98 | title := s.Find("i").Text() 99 | fmt.Printf("Review %d: %s - %s\n", i, band, title) 100 | }) 101 | } 102 | 103 | func main() { 104 | ExampleScrape() 105 | } 106 | ``` 107 | 108 | ## License 109 | 110 | The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic]. 111 | 112 | [jquery]: http://jquery.com/ 113 | [go]: http://golang.org/ 114 | [cascadia]: https://github.com/andybalholm/cascadia 115 | [bsd]: http://opensource.org/licenses/BSD-3-Clause 116 | [golic]: http://golang.org/LICENSE 117 | [caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE 118 | [doc]: http://godoc.org/github.com/PuerkitoBio/goquery 119 | [index]: http://api.jquery.com/index/ 120 | [gonet]: https://github.com/golang/net/ 121 | [html]: http://godoc.org/golang.org/x/net/html 122 | [wiki]: https://github.com/PuerkitoBio/goquery/wiki/Tips-and-tricks 123 | [thatguystone]: https://github.com/thatguystone 124 | [piotr]: https://github.com/piotrkowalczuk 125 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/atom/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build ignore 6 | 7 | package main 8 | 9 | // This program generates table.go and table_test.go. 10 | // Invoke as 11 | // 12 | // go run gen.go |gofmt >table.go 13 | // go run gen.go -test |gofmt >table_test.go 14 | 15 | import ( 16 | "flag" 17 | "fmt" 18 | "math/rand" 19 | "os" 20 | "sort" 21 | "strings" 22 | ) 23 | 24 | // identifier converts s to a Go exported identifier. 25 | // It converts "div" to "Div" and "accept-charset" to "AcceptCharset". 26 | func identifier(s string) string { 27 | b := make([]byte, 0, len(s)) 28 | cap := true 29 | for _, c := range s { 30 | if c == '-' { 31 | cap = true 32 | continue 33 | } 34 | if cap && 'a' <= c && c <= 'z' { 35 | c -= 'a' - 'A' 36 | } 37 | cap = false 38 | b = append(b, byte(c)) 39 | } 40 | return string(b) 41 | } 42 | 43 | var test = flag.Bool("test", false, "generate table_test.go") 44 | 45 | func main() { 46 | flag.Parse() 47 | 48 | var all []string 49 | all = append(all, elements...) 50 | all = append(all, attributes...) 51 | all = append(all, eventHandlers...) 52 | all = append(all, extra...) 53 | sort.Strings(all) 54 | 55 | if *test { 56 | fmt.Printf("// generated by go run gen.go -test; DO NOT EDIT\n\n") 57 | fmt.Printf("package atom\n\n") 58 | fmt.Printf("var testAtomList = []string{\n") 59 | for _, s := range all { 60 | fmt.Printf("\t%q,\n", s) 61 | } 62 | fmt.Printf("}\n") 63 | return 64 | } 65 | 66 | // uniq - lists have dups 67 | // compute max len too 68 | maxLen := 0 69 | w := 0 70 | for _, s := range all { 71 | if w == 0 || all[w-1] != s { 72 | if maxLen < len(s) { 73 | maxLen = len(s) 74 | } 75 | all[w] = s 76 | w++ 77 | } 78 | } 79 | all = all[:w] 80 | 81 | // Find hash that minimizes table size. 82 | var best *table 83 | for i := 0; i < 1000000; i++ { 84 | if best != nil && 1<<(best.k-1) < len(all) { 85 | break 86 | } 87 | h := rand.Uint32() 88 | for k := uint(0); k <= 16; k++ { 89 | if best != nil && k >= best.k { 90 | break 91 | } 92 | var t table 93 | if t.init(h, k, all) { 94 | best = &t 95 | break 96 | } 97 | } 98 | } 99 | if best == nil { 100 | fmt.Fprintf(os.Stderr, "failed to construct string table\n") 101 | os.Exit(1) 102 | } 103 | 104 | // Lay out strings, using overlaps when possible. 105 | layout := append([]string{}, all...) 106 | 107 | // Remove strings that are substrings of other strings 108 | for changed := true; changed; { 109 | changed = false 110 | for i, s := range layout { 111 | if s == "" { 112 | continue 113 | } 114 | for j, t := range layout { 115 | if i != j && t != "" && strings.Contains(s, t) { 116 | changed = true 117 | layout[j] = "" 118 | } 119 | } 120 | } 121 | } 122 | 123 | // Join strings where one suffix matches another prefix. 124 | for { 125 | // Find best i, j, k such that layout[i][len-k:] == layout[j][:k], 126 | // maximizing overlap length k. 127 | besti := -1 128 | bestj := -1 129 | bestk := 0 130 | for i, s := range layout { 131 | if s == "" { 132 | continue 133 | } 134 | for j, t := range layout { 135 | if i == j { 136 | continue 137 | } 138 | for k := bestk + 1; k <= len(s) && k <= len(t); k++ { 139 | if s[len(s)-k:] == t[:k] { 140 | besti = i 141 | bestj = j 142 | bestk = k 143 | } 144 | } 145 | } 146 | } 147 | if bestk > 0 { 148 | layout[besti] += layout[bestj][bestk:] 149 | layout[bestj] = "" 150 | continue 151 | } 152 | break 153 | } 154 | 155 | text := strings.Join(layout, "") 156 | 157 | atom := map[string]uint32{} 158 | for _, s := range all { 159 | off := strings.Index(text, s) 160 | if off < 0 { 161 | panic("lost string " + s) 162 | } 163 | atom[s] = uint32(off<<8 | len(s)) 164 | } 165 | 166 | // Generate the Go code. 167 | fmt.Printf("// generated by go run gen.go; DO NOT EDIT\n\n") 168 | fmt.Printf("package atom\n\nconst (\n") 169 | for _, s := range all { 170 | fmt.Printf("\t%s Atom = %#x\n", identifier(s), atom[s]) 171 | } 172 | fmt.Printf(")\n\n") 173 | 174 | fmt.Printf("const hash0 = %#x\n\n", best.h0) 175 | fmt.Printf("const maxAtomLen = %d\n\n", maxLen) 176 | 177 | fmt.Printf("var table = [1<<%d]Atom{\n", best.k) 178 | for i, s := range best.tab { 179 | if s == "" { 180 | continue 181 | } 182 | fmt.Printf("\t%#x: %#x, // %s\n", i, atom[s], s) 183 | } 184 | fmt.Printf("}\n") 185 | datasize := (1 << best.k) * 4 186 | 187 | fmt.Printf("const atomText =\n") 188 | textsize := len(text) 189 | for len(text) > 60 { 190 | fmt.Printf("\t%q +\n", text[:60]) 191 | text = text[60:] 192 | } 193 | fmt.Printf("\t%q\n\n", text) 194 | 195 | fmt.Fprintf(os.Stderr, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize) 196 | } 197 | 198 | type byLen []string 199 | 200 | func (x byLen) Less(i, j int) bool { return len(x[i]) > len(x[j]) } 201 | func (x byLen) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 202 | func (x byLen) Len() int { return len(x) } 203 | 204 | // fnv computes the FNV hash with an arbitrary starting value h. 205 | func fnv(h uint32, s string) uint32 { 206 | for i := 0; i < len(s); i++ { 207 | h ^= uint32(s[i]) 208 | h *= 16777619 209 | } 210 | return h 211 | } 212 | 213 | // A table represents an attempt at constructing the lookup table. 214 | // The lookup table uses cuckoo hashing, meaning that each string 215 | // can be found in one of two positions. 216 | type table struct { 217 | h0 uint32 218 | k uint 219 | mask uint32 220 | tab []string 221 | } 222 | 223 | // hash returns the two hashes for s. 224 | func (t *table) hash(s string) (h1, h2 uint32) { 225 | h := fnv(t.h0, s) 226 | h1 = h & t.mask 227 | h2 = (h >> 16) & t.mask 228 | return 229 | } 230 | 231 | // init initializes the table with the given parameters. 232 | // h0 is the initial hash value, 233 | // k is the number of bits of hash value to use, and 234 | // x is the list of strings to store in the table. 235 | // init returns false if the table cannot be constructed. 236 | func (t *table) init(h0 uint32, k uint, x []string) bool { 237 | t.h0 = h0 238 | t.k = k 239 | t.tab = make([]string, 1<<k) 240 | t.mask = 1<<k - 1 241 | for _, s := range x { 242 | if !t.insert(s) { 243 | return false 244 | } 245 | } 246 | return true 247 | } 248 | 249 | // insert inserts s in the table. 250 | func (t *table) insert(s string) bool { 251 | h1, h2 := t.hash(s) 252 | if t.tab[h1] == "" { 253 | t.tab[h1] = s 254 | return true 255 | } 256 | if t.tab[h2] == "" { 257 | t.tab[h2] = s 258 | return true 259 | } 260 | if t.push(h1, 0) { 261 | t.tab[h1] = s 262 | return true 263 | } 264 | if t.push(h2, 0) { 265 | t.tab[h2] = s 266 | return true 267 | } 268 | return false 269 | } 270 | 271 | // push attempts to push aside the entry in slot i. 272 | func (t *table) push(i uint32, depth int) bool { 273 | if depth > len(t.tab) { 274 | return false 275 | } 276 | s := t.tab[i] 277 | h1, h2 := t.hash(s) 278 | j := h1 + h2 - i 279 | if t.tab[j] != "" && !t.push(j, depth+1) { 280 | return false 281 | } 282 | t.tab[j] = s 283 | return true 284 | } 285 | 286 | // The lists of element names and attribute keys were taken from 287 | // https://html.spec.whatwg.org/multipage/indices.html#index 288 | // as of the "HTML Living Standard - Last Updated 21 February 2015" version. 289 | 290 | var elements = []string{ 291 | "a", 292 | "abbr", 293 | "address", 294 | "area", 295 | "article", 296 | "aside", 297 | "audio", 298 | "b", 299 | "base", 300 | "bdi", 301 | "bdo", 302 | "blockquote", 303 | "body", 304 | "br", 305 | "button", 306 | "canvas", 307 | "caption", 308 | "cite", 309 | "code", 310 | "col", 311 | "colgroup", 312 | "command", 313 | "data", 314 | "datalist", 315 | "dd", 316 | "del", 317 | "details", 318 | "dfn", 319 | "dialog", 320 | "div", 321 | "dl", 322 | "dt", 323 | "em", 324 | "embed", 325 | "fieldset", 326 | "figcaption", 327 | "figure", 328 | "footer", 329 | "form", 330 | "h1", 331 | "h2", 332 | "h3", 333 | "h4", 334 | "h5", 335 | "h6", 336 | "head", 337 | "header", 338 | "hgroup", 339 | "hr", 340 | "html", 341 | "i", 342 | "iframe", 343 | "img", 344 | "input", 345 | "ins", 346 | "kbd", 347 | "keygen", 348 | "label", 349 | "legend", 350 | "li", 351 | "link", 352 | "map", 353 | "mark", 354 | "menu", 355 | "menuitem", 356 | "meta", 357 | "meter", 358 | "nav", 359 | "noscript", 360 | "object", 361 | "ol", 362 | "optgroup", 363 | "option", 364 | "output", 365 | "p", 366 | "param", 367 | "pre", 368 | "progress", 369 | "q", 370 | "rp", 371 | "rt", 372 | "ruby", 373 | "s", 374 | "samp", 375 | "script", 376 | "section", 377 | "select", 378 | "small", 379 | "source", 380 | "span", 381 | "strong", 382 | "style", 383 | "sub", 384 | "summary", 385 | "sup", 386 | "table", 387 | "tbody", 388 | "td", 389 | "template", 390 | "textarea", 391 | "tfoot", 392 | "th", 393 | "thead", 394 | "time", 395 | "title", 396 | "tr", 397 | "track", 398 | "u", 399 | "ul", 400 | "var", 401 | "video", 402 | "wbr", 403 | } 404 | 405 | // https://html.spec.whatwg.org/multipage/indices.html#attributes-3 406 | 407 | var attributes = []string{ 408 | "abbr", 409 | "accept", 410 | "accept-charset", 411 | "accesskey", 412 | "action", 413 | "alt", 414 | "async", 415 | "autocomplete", 416 | "autofocus", 417 | "autoplay", 418 | "challenge", 419 | "charset", 420 | "checked", 421 | "cite", 422 | "class", 423 | "cols", 424 | "colspan", 425 | "command", 426 | "content", 427 | "contenteditable", 428 | "contextmenu", 429 | "controls", 430 | "coords", 431 | "crossorigin", 432 | "data", 433 | "datetime", 434 | "default", 435 | "defer", 436 | "dir", 437 | "dirname", 438 | "disabled", 439 | "download", 440 | "draggable", 441 | "dropzone", 442 | "enctype", 443 | "for", 444 | "form", 445 | "formaction", 446 | "formenctype", 447 | "formmethod", 448 | "formnovalidate", 449 | "formtarget", 450 | "headers", 451 | "height", 452 | "hidden", 453 | "high", 454 | "href", 455 | "hreflang", 456 | "http-equiv", 457 | "icon", 458 | "id", 459 | "inputmode", 460 | "ismap", 461 | "itemid", 462 | "itemprop", 463 | "itemref", 464 | "itemscope", 465 | "itemtype", 466 | "keytype", 467 | "kind", 468 | "label", 469 | "lang", 470 | "list", 471 | "loop", 472 | "low", 473 | "manifest", 474 | "max", 475 | "maxlength", 476 | "media", 477 | "mediagroup", 478 | "method", 479 | "min", 480 | "minlength", 481 | "multiple", 482 | "muted", 483 | "name", 484 | "novalidate", 485 | "open", 486 | "optimum", 487 | "pattern", 488 | "ping", 489 | "placeholder", 490 | "poster", 491 | "preload", 492 | "radiogroup", 493 | "readonly", 494 | "rel", 495 | "required", 496 | "reversed", 497 | "rows", 498 | "rowspan", 499 | "sandbox", 500 | "spellcheck", 501 | "scope", 502 | "scoped", 503 | "seamless", 504 | "selected", 505 | "shape", 506 | "size", 507 | "sizes", 508 | "sortable", 509 | "sorted", 510 | "span", 511 | "src", 512 | "srcdoc", 513 | "srclang", 514 | "start", 515 | "step", 516 | "style", 517 | "tabindex", 518 | "target", 519 | "title", 520 | "translate", 521 | "type", 522 | "typemustmatch", 523 | "usemap", 524 | "value", 525 | "width", 526 | "wrap", 527 | } 528 | 529 | var eventHandlers = []string{ 530 | "onabort", 531 | "onautocomplete", 532 | "onautocompleteerror", 533 | "onafterprint", 534 | "onbeforeprint", 535 | "onbeforeunload", 536 | "onblur", 537 | "oncancel", 538 | "oncanplay", 539 | "oncanplaythrough", 540 | "onchange", 541 | "onclick", 542 | "onclose", 543 | "oncontextmenu", 544 | "oncuechange", 545 | "ondblclick", 546 | "ondrag", 547 | "ondragend", 548 | "ondragenter", 549 | "ondragleave", 550 | "ondragover", 551 | "ondragstart", 552 | "ondrop", 553 | "ondurationchange", 554 | "onemptied", 555 | "onended", 556 | "onerror", 557 | "onfocus", 558 | "onhashchange", 559 | "oninput", 560 | "oninvalid", 561 | "onkeydown", 562 | "onkeypress", 563 | "onkeyup", 564 | "onlanguagechange", 565 | "onload", 566 | "onloadeddata", 567 | "onloadedmetadata", 568 | "onloadstart", 569 | "onmessage", 570 | "onmousedown", 571 | "onmousemove", 572 | "onmouseout", 573 | "onmouseover", 574 | "onmouseup", 575 | "onmousewheel", 576 | "onoffline", 577 | "ononline", 578 | "onpagehide", 579 | "onpageshow", 580 | "onpause", 581 | "onplay", 582 | "onplaying", 583 | "onpopstate", 584 | "onprogress", 585 | "onratechange", 586 | "onreset", 587 | "onresize", 588 | "onscroll", 589 | "onseeked", 590 | "onseeking", 591 | "onselect", 592 | "onshow", 593 | "onsort", 594 | "onstalled", 595 | "onstorage", 596 | "onsubmit", 597 | "onsuspend", 598 | "ontimeupdate", 599 | "ontoggle", 600 | "onunload", 601 | "onvolumechange", 602 | "onwaiting", 603 | } 604 | 605 | // extra are ad-hoc values not covered by any of the lists above. 606 | var extra = []string{ 607 | "align", 608 | "annotation", 609 | "annotation-xml", 610 | "applet", 611 | "basefont", 612 | "bgsound", 613 | "big", 614 | "blink", 615 | "center", 616 | "color", 617 | "desc", 618 | "face", 619 | "font", 620 | "foreignObject", // HTML is case-insensitive, but SVG-embedded-in-HTML is case-sensitive. 621 | "foreignobject", 622 | "frame", 623 | "frameset", 624 | "image", 625 | "isindex", 626 | "listing", 627 | "malignmark", 628 | "marquee", 629 | "math", 630 | "mglyph", 631 | "mi", 632 | "mn", 633 | "mo", 634 | "ms", 635 | "mtext", 636 | "nobr", 637 | "noembed", 638 | "noframes", 639 | "plaintext", 640 | "prompt", 641 | "public", 642 | "spacer", 643 | "strike", 644 | "svg", 645 | "system", 646 | "tt", 647 | "xmp", 648 | } 649 | -------------------------------------------------------------------------------- /vendor/github.com/andybalholm/cascadia/selector.go: -------------------------------------------------------------------------------- 1 | package cascadia 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | 9 | "golang.org/x/net/html" 10 | ) 11 | 12 | // the Selector type, and functions for creating them 13 | 14 | // A Selector is a function which tells whether a node matches or not. 15 | type Selector func(*html.Node) bool 16 | 17 | // hasChildMatch returns whether n has any child that matches a. 18 | func hasChildMatch(n *html.Node, a Selector) bool { 19 | for c := n.FirstChild; c != nil; c = c.NextSibling { 20 | if a(c) { 21 | return true 22 | } 23 | } 24 | return false 25 | } 26 | 27 | // hasDescendantMatch performs a depth-first search of n's descendants, 28 | // testing whether any of them match a. It returns true as soon as a match is 29 | // found, or false if no match is found. 30 | func hasDescendantMatch(n *html.Node, a Selector) bool { 31 | for c := n.FirstChild; c != nil; c = c.NextSibling { 32 | if a(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) { 33 | return true 34 | } 35 | } 36 | return false 37 | } 38 | 39 | // Compile parses a selector and returns, if successful, a Selector object 40 | // that can be used to match against html.Node objects. 41 | func Compile(sel string) (Selector, error) { 42 | p := &parser{s: sel} 43 | compiled, err := p.parseSelectorGroup() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | if p.i < len(sel) { 49 | return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i) 50 | } 51 | 52 | return compiled, nil 53 | } 54 | 55 | // MustCompile is like Compile, but panics instead of returning an error. 56 | func MustCompile(sel string) Selector { 57 | compiled, err := Compile(sel) 58 | if err != nil { 59 | panic(err) 60 | } 61 | return compiled 62 | } 63 | 64 | // MatchAll returns a slice of the nodes that match the selector, 65 | // from n and its children. 66 | func (s Selector) MatchAll(n *html.Node) []*html.Node { 67 | return s.matchAllInto(n, nil) 68 | } 69 | 70 | func (s Selector) matchAllInto(n *html.Node, storage []*html.Node) []*html.Node { 71 | if s(n) { 72 | storage = append(storage, n) 73 | } 74 | 75 | for child := n.FirstChild; child != nil; child = child.NextSibling { 76 | storage = s.matchAllInto(child, storage) 77 | } 78 | 79 | return storage 80 | } 81 | 82 | // Match returns true if the node matches the selector. 83 | func (s Selector) Match(n *html.Node) bool { 84 | return s(n) 85 | } 86 | 87 | // MatchFirst returns the first node that matches s, from n and its children. 88 | func (s Selector) MatchFirst(n *html.Node) *html.Node { 89 | if s.Match(n) { 90 | return n 91 | } 92 | 93 | for c := n.FirstChild; c != nil; c = c.NextSibling { 94 | m := s.MatchFirst(c) 95 | if m != nil { 96 | return m 97 | } 98 | } 99 | return nil 100 | } 101 | 102 | // Filter returns the nodes in nodes that match the selector. 103 | func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) { 104 | for _, n := range nodes { 105 | if s(n) { 106 | result = append(result, n) 107 | } 108 | } 109 | return result 110 | } 111 | 112 | // typeSelector returns a Selector that matches elements with a given tag name. 113 | func typeSelector(tag string) Selector { 114 | tag = toLowerASCII(tag) 115 | return func(n *html.Node) bool { 116 | return n.Type == html.ElementNode && n.Data == tag 117 | } 118 | } 119 | 120 | // toLowerASCII returns s with all ASCII capital letters lowercased. 121 | func toLowerASCII(s string) string { 122 | var b []byte 123 | for i := 0; i < len(s); i++ { 124 | if c := s[i]; 'A' <= c && c <= 'Z' { 125 | if b == nil { 126 | b = make([]byte, len(s)) 127 | copy(b, s) 128 | } 129 | b[i] = s[i] + ('a' - 'A') 130 | } 131 | } 132 | 133 | if b == nil { 134 | return s 135 | } 136 | 137 | return string(b) 138 | } 139 | 140 | // attributeSelector returns a Selector that matches elements 141 | // where the attribute named key satisifes the function f. 142 | func attributeSelector(key string, f func(string) bool) Selector { 143 | key = toLowerASCII(key) 144 | return func(n *html.Node) bool { 145 | if n.Type != html.ElementNode { 146 | return false 147 | } 148 | for _, a := range n.Attr { 149 | if a.Key == key && f(a.Val) { 150 | return true 151 | } 152 | } 153 | return false 154 | } 155 | } 156 | 157 | // attributeExistsSelector returns a Selector that matches elements that have 158 | // an attribute named key. 159 | func attributeExistsSelector(key string) Selector { 160 | return attributeSelector(key, func(string) bool { return true }) 161 | } 162 | 163 | // attributeEqualsSelector returns a Selector that matches elements where 164 | // the attribute named key has the value val. 165 | func attributeEqualsSelector(key, val string) Selector { 166 | return attributeSelector(key, 167 | func(s string) bool { 168 | return s == val 169 | }) 170 | } 171 | 172 | // attributeNotEqualSelector returns a Selector that matches elements where 173 | // the attribute named key does not have the value val. 174 | func attributeNotEqualSelector(key, val string) Selector { 175 | key = toLowerASCII(key) 176 | return func(n *html.Node) bool { 177 | if n.Type != html.ElementNode { 178 | return false 179 | } 180 | for _, a := range n.Attr { 181 | if a.Key == key && a.Val == val { 182 | return false 183 | } 184 | } 185 | return true 186 | } 187 | } 188 | 189 | // attributeIncludesSelector returns a Selector that matches elements where 190 | // the attribute named key is a whitespace-separated list that includes val. 191 | func attributeIncludesSelector(key, val string) Selector { 192 | return attributeSelector(key, 193 | func(s string) bool { 194 | for s != "" { 195 | i := strings.IndexAny(s, " \t\r\n\f") 196 | if i == -1 { 197 | return s == val 198 | } 199 | if s[:i] == val { 200 | return true 201 | } 202 | s = s[i+1:] 203 | } 204 | return false 205 | }) 206 | } 207 | 208 | // attributeDashmatchSelector returns a Selector that matches elements where 209 | // the attribute named key equals val or starts with val plus a hyphen. 210 | func attributeDashmatchSelector(key, val string) Selector { 211 | return attributeSelector(key, 212 | func(s string) bool { 213 | if s == val { 214 | return true 215 | } 216 | if len(s) <= len(val) { 217 | return false 218 | } 219 | if s[:len(val)] == val && s[len(val)] == '-' { 220 | return true 221 | } 222 | return false 223 | }) 224 | } 225 | 226 | // attributePrefixSelector returns a Selector that matches elements where 227 | // the attribute named key starts with val. 228 | func attributePrefixSelector(key, val string) Selector { 229 | return attributeSelector(key, 230 | func(s string) bool { 231 | if strings.TrimSpace(s) == "" { 232 | return false 233 | } 234 | return strings.HasPrefix(s, val) 235 | }) 236 | } 237 | 238 | // attributeSuffixSelector returns a Selector that matches elements where 239 | // the attribute named key ends with val. 240 | func attributeSuffixSelector(key, val string) Selector { 241 | return attributeSelector(key, 242 | func(s string) bool { 243 | if strings.TrimSpace(s) == "" { 244 | return false 245 | } 246 | return strings.HasSuffix(s, val) 247 | }) 248 | } 249 | 250 | // attributeSubstringSelector returns a Selector that matches nodes where 251 | // the attribute named key contains val. 252 | func attributeSubstringSelector(key, val string) Selector { 253 | return attributeSelector(key, 254 | func(s string) bool { 255 | if strings.TrimSpace(s) == "" { 256 | return false 257 | } 258 | return strings.Contains(s, val) 259 | }) 260 | } 261 | 262 | // attributeRegexSelector returns a Selector that matches nodes where 263 | // the attribute named key matches the regular expression rx 264 | func attributeRegexSelector(key string, rx *regexp.Regexp) Selector { 265 | return attributeSelector(key, 266 | func(s string) bool { 267 | return rx.MatchString(s) 268 | }) 269 | } 270 | 271 | // intersectionSelector returns a selector that matches nodes that match 272 | // both a and b. 273 | func intersectionSelector(a, b Selector) Selector { 274 | return func(n *html.Node) bool { 275 | return a(n) && b(n) 276 | } 277 | } 278 | 279 | // unionSelector returns a selector that matches elements that match 280 | // either a or b. 281 | func unionSelector(a, b Selector) Selector { 282 | return func(n *html.Node) bool { 283 | return a(n) || b(n) 284 | } 285 | } 286 | 287 | // negatedSelector returns a selector that matches elements that do not match a. 288 | func negatedSelector(a Selector) Selector { 289 | return func(n *html.Node) bool { 290 | if n.Type != html.ElementNode { 291 | return false 292 | } 293 | return !a(n) 294 | } 295 | } 296 | 297 | // writeNodeText writes the text contained in n and its descendants to b. 298 | func writeNodeText(n *html.Node, b *bytes.Buffer) { 299 | switch n.Type { 300 | case html.TextNode: 301 | b.WriteString(n.Data) 302 | case html.ElementNode: 303 | for c := n.FirstChild; c != nil; c = c.NextSibling { 304 | writeNodeText(c, b) 305 | } 306 | } 307 | } 308 | 309 | // nodeText returns the text contained in n and its descendants. 310 | func nodeText(n *html.Node) string { 311 | var b bytes.Buffer 312 | writeNodeText(n, &b) 313 | return b.String() 314 | } 315 | 316 | // nodeOwnText returns the contents of the text nodes that are direct 317 | // children of n. 318 | func nodeOwnText(n *html.Node) string { 319 | var b bytes.Buffer 320 | for c := n.FirstChild; c != nil; c = c.NextSibling { 321 | if c.Type == html.TextNode { 322 | b.WriteString(c.Data) 323 | } 324 | } 325 | return b.String() 326 | } 327 | 328 | // textSubstrSelector returns a selector that matches nodes that 329 | // contain the given text. 330 | func textSubstrSelector(val string) Selector { 331 | return func(n *html.Node) bool { 332 | text := strings.ToLower(nodeText(n)) 333 | return strings.Contains(text, val) 334 | } 335 | } 336 | 337 | // ownTextSubstrSelector returns a selector that matches nodes that 338 | // directly contain the given text 339 | func ownTextSubstrSelector(val string) Selector { 340 | return func(n *html.Node) bool { 341 | text := strings.ToLower(nodeOwnText(n)) 342 | return strings.Contains(text, val) 343 | } 344 | } 345 | 346 | // textRegexSelector returns a selector that matches nodes whose text matches 347 | // the specified regular expression 348 | func textRegexSelector(rx *regexp.Regexp) Selector { 349 | return func(n *html.Node) bool { 350 | return rx.MatchString(nodeText(n)) 351 | } 352 | } 353 | 354 | // ownTextRegexSelector returns a selector that matches nodes whose text 355 | // directly matches the specified regular expression 356 | func ownTextRegexSelector(rx *regexp.Regexp) Selector { 357 | return func(n *html.Node) bool { 358 | return rx.MatchString(nodeOwnText(n)) 359 | } 360 | } 361 | 362 | // hasChildSelector returns a selector that matches elements 363 | // with a child that matches a. 364 | func hasChildSelector(a Selector) Selector { 365 | return func(n *html.Node) bool { 366 | if n.Type != html.ElementNode { 367 | return false 368 | } 369 | return hasChildMatch(n, a) 370 | } 371 | } 372 | 373 | // hasDescendantSelector returns a selector that matches elements 374 | // with any descendant that matches a. 375 | func hasDescendantSelector(a Selector) Selector { 376 | return func(n *html.Node) bool { 377 | if n.Type != html.ElementNode { 378 | return false 379 | } 380 | return hasDescendantMatch(n, a) 381 | } 382 | } 383 | 384 | // nthChildSelector returns a selector that implements :nth-child(an+b). 385 | // If last is true, implements :nth-last-child instead. 386 | // If ofType is true, implements :nth-of-type instead. 387 | func nthChildSelector(a, b int, last, ofType bool) Selector { 388 | return func(n *html.Node) bool { 389 | if n.Type != html.ElementNode { 390 | return false 391 | } 392 | 393 | parent := n.Parent 394 | if parent == nil { 395 | return false 396 | } 397 | 398 | if parent.Type == html.DocumentNode { 399 | return false 400 | } 401 | 402 | i := -1 403 | count := 0 404 | for c := parent.FirstChild; c != nil; c = c.NextSibling { 405 | if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) { 406 | continue 407 | } 408 | count++ 409 | if c == n { 410 | i = count 411 | if !last { 412 | break 413 | } 414 | } 415 | } 416 | 417 | if i == -1 { 418 | // This shouldn't happen, since n should always be one of its parent's children. 419 | return false 420 | } 421 | 422 | if last { 423 | i = count - i + 1 424 | } 425 | 426 | i -= b 427 | if a == 0 { 428 | return i == 0 429 | } 430 | 431 | return i%a == 0 && i/a >= 0 432 | } 433 | } 434 | 435 | // simpleNthChildSelector returns a selector that implements :nth-child(b). 436 | // If ofType is true, implements :nth-of-type instead. 437 | func simpleNthChildSelector(b int, ofType bool) Selector { 438 | return func(n *html.Node) bool { 439 | if n.Type != html.ElementNode { 440 | return false 441 | } 442 | 443 | parent := n.Parent 444 | if parent == nil { 445 | return false 446 | } 447 | 448 | if parent.Type == html.DocumentNode { 449 | return false 450 | } 451 | 452 | count := 0 453 | for c := parent.FirstChild; c != nil; c = c.NextSibling { 454 | if c.Type != html.ElementNode || (ofType && c.Data != n.Data) { 455 | continue 456 | } 457 | count++ 458 | if c == n { 459 | return count == b 460 | } 461 | if count >= b { 462 | return false 463 | } 464 | } 465 | return false 466 | } 467 | } 468 | 469 | // simpleNthLastChildSelector returns a selector that implements 470 | // :nth-last-child(b). If ofType is true, implements :nth-last-of-type 471 | // instead. 472 | func simpleNthLastChildSelector(b int, ofType bool) Selector { 473 | return func(n *html.Node) bool { 474 | if n.Type != html.ElementNode { 475 | return false 476 | } 477 | 478 | parent := n.Parent 479 | if parent == nil { 480 | return false 481 | } 482 | 483 | if parent.Type == html.DocumentNode { 484 | return false 485 | } 486 | 487 | count := 0 488 | for c := parent.LastChild; c != nil; c = c.PrevSibling { 489 | if c.Type != html.ElementNode || (ofType && c.Data != n.Data) { 490 | continue 491 | } 492 | count++ 493 | if c == n { 494 | return count == b 495 | } 496 | if count >= b { 497 | return false 498 | } 499 | } 500 | return false 501 | } 502 | } 503 | 504 | // onlyChildSelector returns a selector that implements :only-child. 505 | // If ofType is true, it implements :only-of-type instead. 506 | func onlyChildSelector(ofType bool) Selector { 507 | return func(n *html.Node) bool { 508 | if n.Type != html.ElementNode { 509 | return false 510 | } 511 | 512 | parent := n.Parent 513 | if parent == nil { 514 | return false 515 | } 516 | 517 | if parent.Type == html.DocumentNode { 518 | return false 519 | } 520 | 521 | count := 0 522 | for c := parent.FirstChild; c != nil; c = c.NextSibling { 523 | if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) { 524 | continue 525 | } 526 | count++ 527 | if count > 1 { 528 | return false 529 | } 530 | } 531 | 532 | return count == 1 533 | } 534 | } 535 | 536 | // inputSelector is a Selector that matches input, select, textarea and button elements. 537 | func inputSelector(n *html.Node) bool { 538 | return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button") 539 | } 540 | 541 | // emptyElementSelector is a Selector that matches empty elements. 542 | func emptyElementSelector(n *html.Node) bool { 543 | if n.Type != html.ElementNode { 544 | return false 545 | } 546 | 547 | for c := n.FirstChild; c != nil; c = c.NextSibling { 548 | switch c.Type { 549 | case html.ElementNode, html.TextNode: 550 | return false 551 | } 552 | } 553 | 554 | return true 555 | } 556 | 557 | // descendantSelector returns a Selector that matches an element if 558 | // it matches d and has an ancestor that matches a. 559 | func descendantSelector(a, d Selector) Selector { 560 | return func(n *html.Node) bool { 561 | if !d(n) { 562 | return false 563 | } 564 | 565 | for p := n.Parent; p != nil; p = p.Parent { 566 | if a(p) { 567 | return true 568 | } 569 | } 570 | 571 | return false 572 | } 573 | } 574 | 575 | // childSelector returns a Selector that matches an element if 576 | // it matches d and its parent matches a. 577 | func childSelector(a, d Selector) Selector { 578 | return func(n *html.Node) bool { 579 | return d(n) && n.Parent != nil && a(n.Parent) 580 | } 581 | } 582 | 583 | // siblingSelector returns a Selector that matches an element 584 | // if it matches s2 and in is preceded by an element that matches s1. 585 | // If adjacent is true, the sibling must be immediately before the element. 586 | func siblingSelector(s1, s2 Selector, adjacent bool) Selector { 587 | return func(n *html.Node) bool { 588 | if !s2(n) { 589 | return false 590 | } 591 | 592 | if adjacent { 593 | for n = n.PrevSibling; n != nil; n = n.PrevSibling { 594 | if n.Type == html.TextNode || n.Type == html.CommentNode { 595 | continue 596 | } 597 | return s1(n) 598 | } 599 | return false 600 | } 601 | 602 | // Walk backwards looking for element that matches s1 603 | for c := n.PrevSibling; c != nil; c = c.PrevSibling { 604 | if s1(c) { 605 | return true 606 | } 607 | } 608 | 609 | return false 610 | } 611 | } 612 | 613 | // rootSelector implements :root 614 | func rootSelector(n *html.Node) bool { 615 | if n.Type != html.ElementNode { 616 | return false 617 | } 618 | if n.Parent == nil { 619 | return false 620 | } 621 | return n.Parent.Type == html.DocumentNode 622 | } 623 | -------------------------------------------------------------------------------- /vendor/github.com/PuerkitoBio/goquery/manipulation.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "strings" 5 | 6 | "golang.org/x/net/html" 7 | ) 8 | 9 | // After applies the selector from the root document and inserts the matched elements 10 | // after the elements in the set of matched elements. 11 | // 12 | // If one of the matched elements in the selection is not currently in the 13 | // document, it's impossible to insert nodes after it, so it will be ignored. 14 | // 15 | // This follows the same rules as Selection.Append. 16 | func (s *Selection) After(selector string) *Selection { 17 | return s.AfterMatcher(compileMatcher(selector)) 18 | } 19 | 20 | // AfterMatcher applies the matcher from the root document and inserts the matched elements 21 | // after the elements in the set of matched elements. 22 | // 23 | // If one of the matched elements in the selection is not currently in the 24 | // document, it's impossible to insert nodes after it, so it will be ignored. 25 | // 26 | // This follows the same rules as Selection.Append. 27 | func (s *Selection) AfterMatcher(m Matcher) *Selection { 28 | return s.AfterNodes(m.MatchAll(s.document.rootNode)...) 29 | } 30 | 31 | // AfterSelection inserts the elements in the selection after each element in the set of matched 32 | // elements. 33 | // 34 | // This follows the same rules as Selection.Append. 35 | func (s *Selection) AfterSelection(sel *Selection) *Selection { 36 | return s.AfterNodes(sel.Nodes...) 37 | } 38 | 39 | // AfterHtml parses the html and inserts it after the set of matched elements. 40 | // 41 | // This follows the same rules as Selection.Append. 42 | func (s *Selection) AfterHtml(html string) *Selection { 43 | return s.AfterNodes(parseHtml(html)...) 44 | } 45 | 46 | // AfterNodes inserts the nodes after each element in the set of matched elements. 47 | // 48 | // This follows the same rules as Selection.Append. 49 | func (s *Selection) AfterNodes(ns ...*html.Node) *Selection { 50 | return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) { 51 | if sn.Parent != nil { 52 | sn.Parent.InsertBefore(n, sn.NextSibling) 53 | } 54 | }) 55 | } 56 | 57 | // Append appends the elements specified by the selector to the end of each element 58 | // in the set of matched elements, following those rules: 59 | // 60 | // 1) The selector is applied to the root document. 61 | // 62 | // 2) Elements that are part of the document will be moved to the new location. 63 | // 64 | // 3) If there are multiple locations to append to, cloned nodes will be 65 | // appended to all target locations except the last one, which will be moved 66 | // as noted in (2). 67 | func (s *Selection) Append(selector string) *Selection { 68 | return s.AppendMatcher(compileMatcher(selector)) 69 | } 70 | 71 | // AppendMatcher appends the elements specified by the matcher to the end of each element 72 | // in the set of matched elements. 73 | // 74 | // This follows the same rules as Selection.Append. 75 | func (s *Selection) AppendMatcher(m Matcher) *Selection { 76 | return s.AppendNodes(m.MatchAll(s.document.rootNode)...) 77 | } 78 | 79 | // AppendSelection appends the elements in the selection to the end of each element 80 | // in the set of matched elements. 81 | // 82 | // This follows the same rules as Selection.Append. 83 | func (s *Selection) AppendSelection(sel *Selection) *Selection { 84 | return s.AppendNodes(sel.Nodes...) 85 | } 86 | 87 | // AppendHtml parses the html and appends it to the set of matched elements. 88 | func (s *Selection) AppendHtml(html string) *Selection { 89 | return s.AppendNodes(parseHtml(html)...) 90 | } 91 | 92 | // AppendNodes appends the specified nodes to each node in the set of matched elements. 93 | // 94 | // This follows the same rules as Selection.Append. 95 | func (s *Selection) AppendNodes(ns ...*html.Node) *Selection { 96 | return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) { 97 | sn.AppendChild(n) 98 | }) 99 | } 100 | 101 | // Before inserts the matched elements before each element in the set of matched elements. 102 | // 103 | // This follows the same rules as Selection.Append. 104 | func (s *Selection) Before(selector string) *Selection { 105 | return s.BeforeMatcher(compileMatcher(selector)) 106 | } 107 | 108 | // BeforeMatcher inserts the matched elements before each element in the set of matched elements. 109 | // 110 | // This follows the same rules as Selection.Append. 111 | func (s *Selection) BeforeMatcher(m Matcher) *Selection { 112 | return s.BeforeNodes(m.MatchAll(s.document.rootNode)...) 113 | } 114 | 115 | // BeforeSelection inserts the elements in the selection before each element in the set of matched 116 | // elements. 117 | // 118 | // This follows the same rules as Selection.Append. 119 | func (s *Selection) BeforeSelection(sel *Selection) *Selection { 120 | return s.BeforeNodes(sel.Nodes...) 121 | } 122 | 123 | // BeforeHtml parses the html and inserts it before the set of matched elements. 124 | // 125 | // This follows the same rules as Selection.Append. 126 | func (s *Selection) BeforeHtml(html string) *Selection { 127 | return s.BeforeNodes(parseHtml(html)...) 128 | } 129 | 130 | // BeforeNodes inserts the nodes before each element in the set of matched elements. 131 | // 132 | // This follows the same rules as Selection.Append. 133 | func (s *Selection) BeforeNodes(ns ...*html.Node) *Selection { 134 | return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) { 135 | if sn.Parent != nil { 136 | sn.Parent.InsertBefore(n, sn) 137 | } 138 | }) 139 | } 140 | 141 | // Clone creates a deep copy of the set of matched nodes. The new nodes will not be 142 | // attached to the document. 143 | func (s *Selection) Clone() *Selection { 144 | ns := newEmptySelection(s.document) 145 | ns.Nodes = cloneNodes(s.Nodes) 146 | return ns 147 | } 148 | 149 | // Empty removes all children nodes from the set of matched elements. 150 | // It returns the children nodes in a new Selection. 151 | func (s *Selection) Empty() *Selection { 152 | var nodes []*html.Node 153 | 154 | for _, n := range s.Nodes { 155 | for c := n.FirstChild; c != nil; c = n.FirstChild { 156 | n.RemoveChild(c) 157 | nodes = append(nodes, c) 158 | } 159 | } 160 | 161 | return pushStack(s, nodes) 162 | } 163 | 164 | // Prepend prepends the elements specified by the selector to each element in 165 | // the set of matched elements, following the same rules as Append. 166 | func (s *Selection) Prepend(selector string) *Selection { 167 | return s.PrependMatcher(compileMatcher(selector)) 168 | } 169 | 170 | // PrependMatcher prepends the elements specified by the matcher to each 171 | // element in the set of matched elements. 172 | // 173 | // This follows the same rules as Selection.Append. 174 | func (s *Selection) PrependMatcher(m Matcher) *Selection { 175 | return s.PrependNodes(m.MatchAll(s.document.rootNode)...) 176 | } 177 | 178 | // PrependSelection prepends the elements in the selection to each element in 179 | // the set of matched elements. 180 | // 181 | // This follows the same rules as Selection.Append. 182 | func (s *Selection) PrependSelection(sel *Selection) *Selection { 183 | return s.PrependNodes(sel.Nodes...) 184 | } 185 | 186 | // PrependHtml parses the html and prepends it to the set of matched elements. 187 | func (s *Selection) PrependHtml(html string) *Selection { 188 | return s.PrependNodes(parseHtml(html)...) 189 | } 190 | 191 | // PrependNodes prepends the specified nodes to each node in the set of 192 | // matched elements. 193 | // 194 | // This follows the same rules as Selection.Append. 195 | func (s *Selection) PrependNodes(ns ...*html.Node) *Selection { 196 | return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) { 197 | // sn.FirstChild may be nil, in which case this functions like 198 | // sn.AppendChild() 199 | sn.InsertBefore(n, sn.FirstChild) 200 | }) 201 | } 202 | 203 | // Remove removes the set of matched elements from the document. 204 | // It returns the same selection, now consisting of nodes not in the document. 205 | func (s *Selection) Remove() *Selection { 206 | for _, n := range s.Nodes { 207 | if n.Parent != nil { 208 | n.Parent.RemoveChild(n) 209 | } 210 | } 211 | 212 | return s 213 | } 214 | 215 | // RemoveFiltered removes the set of matched elements by selector. 216 | // It returns the Selection of removed nodes. 217 | func (s *Selection) RemoveFiltered(selector string) *Selection { 218 | return s.RemoveMatcher(compileMatcher(selector)) 219 | } 220 | 221 | // RemoveMatcher removes the set of matched elements. 222 | // It returns the Selection of removed nodes. 223 | func (s *Selection) RemoveMatcher(m Matcher) *Selection { 224 | return s.FilterMatcher(m).Remove() 225 | } 226 | 227 | // ReplaceWith replaces each element in the set of matched elements with the 228 | // nodes matched by the given selector. 229 | // It returns the removed elements. 230 | // 231 | // This follows the same rules as Selection.Append. 232 | func (s *Selection) ReplaceWith(selector string) *Selection { 233 | return s.ReplaceWithMatcher(compileMatcher(selector)) 234 | } 235 | 236 | // ReplaceWithMatcher replaces each element in the set of matched elements with 237 | // the nodes matched by the given Matcher. 238 | // It returns the removed elements. 239 | // 240 | // This follows the same rules as Selection.Append. 241 | func (s *Selection) ReplaceWithMatcher(m Matcher) *Selection { 242 | return s.ReplaceWithNodes(m.MatchAll(s.document.rootNode)...) 243 | } 244 | 245 | // ReplaceWithSelection replaces each element in the set of matched elements with 246 | // the nodes from the given Selection. 247 | // It returns the removed elements. 248 | // 249 | // This follows the same rules as Selection.Append. 250 | func (s *Selection) ReplaceWithSelection(sel *Selection) *Selection { 251 | return s.ReplaceWithNodes(sel.Nodes...) 252 | } 253 | 254 | // ReplaceWithHtml replaces each element in the set of matched elements with 255 | // the parsed HTML. 256 | // It returns the removed elements. 257 | // 258 | // This follows the same rules as Selection.Append. 259 | func (s *Selection) ReplaceWithHtml(html string) *Selection { 260 | return s.ReplaceWithNodes(parseHtml(html)...) 261 | } 262 | 263 | // ReplaceWithNodes replaces each element in the set of matched elements with 264 | // the given nodes. 265 | // It returns the removed elements. 266 | // 267 | // This follows the same rules as Selection.Append. 268 | func (s *Selection) ReplaceWithNodes(ns ...*html.Node) *Selection { 269 | s.AfterNodes(ns...) 270 | return s.Remove() 271 | } 272 | 273 | // Set the html content of each element in the selection to specified html string. 274 | func (s *Selection) SetHtml(html string) *Selection { 275 | return setHtmlNodes(s, parseHtml(html)...) 276 | } 277 | 278 | // Set the content of each element in the selection to specified content. The 279 | // provided text string is escaped. 280 | func (s *Selection) SetText(text string) *Selection { 281 | return s.SetHtml(html.EscapeString(text)) 282 | } 283 | 284 | // Unwrap removes the parents of the set of matched elements, leaving the matched 285 | // elements (and their siblings, if any) in their place. 286 | // It returns the original selection. 287 | func (s *Selection) Unwrap() *Selection { 288 | s.Parent().Each(func(i int, ss *Selection) { 289 | // For some reason, jquery allows unwrap to remove the <head> element, so 290 | // allowing it here too. Same for <html>. Why it allows those elements to 291 | // be unwrapped while not allowing body is a mystery to me. 292 | if ss.Nodes[0].Data != "body" { 293 | ss.ReplaceWithSelection(ss.Contents()) 294 | } 295 | }) 296 | 297 | return s 298 | } 299 | 300 | // Wrap wraps each element in the set of matched elements inside the first 301 | // element matched by the given selector. The matched child is cloned before 302 | // being inserted into the document. 303 | // 304 | // It returns the original set of elements. 305 | func (s *Selection) Wrap(selector string) *Selection { 306 | return s.WrapMatcher(compileMatcher(selector)) 307 | } 308 | 309 | // WrapMatcher wraps each element in the set of matched elements inside the 310 | // first element matched by the given matcher. The matched child is cloned 311 | // before being inserted into the document. 312 | // 313 | // It returns the original set of elements. 314 | func (s *Selection) WrapMatcher(m Matcher) *Selection { 315 | return s.wrapNodes(m.MatchAll(s.document.rootNode)...) 316 | } 317 | 318 | // WrapSelection wraps each element in the set of matched elements inside the 319 | // first element in the given Selection. The element is cloned before being 320 | // inserted into the document. 321 | // 322 | // It returns the original set of elements. 323 | func (s *Selection) WrapSelection(sel *Selection) *Selection { 324 | return s.wrapNodes(sel.Nodes...) 325 | } 326 | 327 | // WrapHtml wraps each element in the set of matched elements inside the inner- 328 | // most child of the given HTML. 329 | // 330 | // It returns the original set of elements. 331 | func (s *Selection) WrapHtml(html string) *Selection { 332 | return s.wrapNodes(parseHtml(html)...) 333 | } 334 | 335 | // WrapNode wraps each element in the set of matched elements inside the inner- 336 | // most child of the given node. The given node is copied before being inserted 337 | // into the document. 338 | // 339 | // It returns the original set of elements. 340 | func (s *Selection) WrapNode(n *html.Node) *Selection { 341 | return s.wrapNodes(n) 342 | } 343 | 344 | func (s *Selection) wrapNodes(ns ...*html.Node) *Selection { 345 | s.Each(func(i int, ss *Selection) { 346 | ss.wrapAllNodes(ns...) 347 | }) 348 | 349 | return s 350 | } 351 | 352 | // WrapAll wraps a single HTML structure, matched by the given selector, around 353 | // all elements in the set of matched elements. The matched child is cloned 354 | // before being inserted into the document. 355 | // 356 | // It returns the original set of elements. 357 | func (s *Selection) WrapAll(selector string) *Selection { 358 | return s.WrapAllMatcher(compileMatcher(selector)) 359 | } 360 | 361 | // WrapAllMatcher wraps a single HTML structure, matched by the given Matcher, 362 | // around all elements in the set of matched elements. The matched child is 363 | // cloned before being inserted into the document. 364 | // 365 | // It returns the original set of elements. 366 | func (s *Selection) WrapAllMatcher(m Matcher) *Selection { 367 | return s.wrapAllNodes(m.MatchAll(s.document.rootNode)...) 368 | } 369 | 370 | // WrapAllSelection wraps a single HTML structure, the first node of the given 371 | // Selection, around all elements in the set of matched elements. The matched 372 | // child is cloned before being inserted into the document. 373 | // 374 | // It returns the original set of elements. 375 | func (s *Selection) WrapAllSelection(sel *Selection) *Selection { 376 | return s.wrapAllNodes(sel.Nodes...) 377 | } 378 | 379 | // WrapAllHtml wraps the given HTML structure around all elements in the set of 380 | // matched elements. The matched child is cloned before being inserted into the 381 | // document. 382 | // 383 | // It returns the original set of elements. 384 | func (s *Selection) WrapAllHtml(html string) *Selection { 385 | return s.wrapAllNodes(parseHtml(html)...) 386 | } 387 | 388 | func (s *Selection) wrapAllNodes(ns ...*html.Node) *Selection { 389 | if len(ns) > 0 { 390 | return s.WrapAllNode(ns[0]) 391 | } 392 | return s 393 | } 394 | 395 | // WrapAllNode wraps the given node around the first element in the Selection, 396 | // making all other nodes in the Selection children of the given node. The node 397 | // is cloned before being inserted into the document. 398 | // 399 | // It returns the original set of elements. 400 | func (s *Selection) WrapAllNode(n *html.Node) *Selection { 401 | if s.Size() == 0 { 402 | return s 403 | } 404 | 405 | wrap := cloneNode(n) 406 | 407 | first := s.Nodes[0] 408 | if first.Parent != nil { 409 | first.Parent.InsertBefore(wrap, first) 410 | first.Parent.RemoveChild(first) 411 | } 412 | 413 | for c := getFirstChildEl(wrap); c != nil; c = getFirstChildEl(wrap) { 414 | wrap = c 415 | } 416 | 417 | newSingleSelection(wrap, s.document).AppendSelection(s) 418 | 419 | return s 420 | } 421 | 422 | // WrapInner wraps an HTML structure, matched by the given selector, around the 423 | // content of element in the set of matched elements. The matched child is 424 | // cloned before being inserted into the document. 425 | // 426 | // It returns the original set of elements. 427 | func (s *Selection) WrapInner(selector string) *Selection { 428 | return s.WrapInnerMatcher(compileMatcher(selector)) 429 | } 430 | 431 | // WrapInnerMatcher wraps an HTML structure, matched by the given selector, 432 | // around the content of element in the set of matched elements. The matched 433 | // child is cloned before being inserted into the document. 434 | // 435 | // It returns the original set of elements. 436 | func (s *Selection) WrapInnerMatcher(m Matcher) *Selection { 437 | return s.wrapInnerNodes(m.MatchAll(s.document.rootNode)...) 438 | } 439 | 440 | // WrapInnerSelection wraps an HTML structure, matched by the given selector, 441 | // around the content of element in the set of matched elements. The matched 442 | // child is cloned before being inserted into the document. 443 | // 444 | // It returns the original set of elements. 445 | func (s *Selection) WrapInnerSelection(sel *Selection) *Selection { 446 | return s.wrapInnerNodes(sel.Nodes...) 447 | } 448 | 449 | // WrapInnerHtml wraps an HTML structure, matched by the given selector, around 450 | // the content of element in the set of matched elements. The matched child is 451 | // cloned before being inserted into the document. 452 | // 453 | // It returns the original set of elements. 454 | func (s *Selection) WrapInnerHtml(html string) *Selection { 455 | return s.wrapInnerNodes(parseHtml(html)...) 456 | } 457 | 458 | // WrapInnerNode wraps an HTML structure, matched by the given selector, around 459 | // the content of element in the set of matched elements. The matched child is 460 | // cloned before being inserted into the document. 461 | // 462 | // It returns the original set of elements. 463 | func (s *Selection) WrapInnerNode(n *html.Node) *Selection { 464 | return s.wrapInnerNodes(n) 465 | } 466 | 467 | func (s *Selection) wrapInnerNodes(ns ...*html.Node) *Selection { 468 | if len(ns) == 0 { 469 | return s 470 | } 471 | 472 | s.Each(func(i int, s *Selection) { 473 | contents := s.Contents() 474 | 475 | if contents.Size() > 0 { 476 | contents.wrapAllNodes(ns...) 477 | } else { 478 | s.AppendNodes(cloneNode(ns[0])) 479 | } 480 | }) 481 | 482 | return s 483 | } 484 | 485 | func parseHtml(h string) []*html.Node { 486 | // Errors are only returned when the io.Reader returns any error besides 487 | // EOF, but strings.Reader never will 488 | nodes, err := html.ParseFragment(strings.NewReader(h), &html.Node{Type: html.ElementNode}) 489 | if err != nil { 490 | panic("goquery: failed to parse HTML: " + err.Error()) 491 | } 492 | return nodes 493 | } 494 | 495 | func setHtmlNodes(s *Selection, ns ...*html.Node) *Selection { 496 | for _, n := range s.Nodes { 497 | for c := n.FirstChild; c != nil; c = n.FirstChild { 498 | n.RemoveChild(c) 499 | } 500 | for _, c := range ns { 501 | n.AppendChild(cloneNode(c)) 502 | } 503 | } 504 | return s 505 | } 506 | 507 | // Get the first child that is an ElementNode 508 | func getFirstChildEl(n *html.Node) *html.Node { 509 | c := n.FirstChild 510 | for c != nil && c.Type != html.ElementNode { 511 | c = c.NextSibling 512 | } 513 | return c 514 | } 515 | 516 | // Deep copy a slice of nodes. 517 | func cloneNodes(ns []*html.Node) []*html.Node { 518 | cns := make([]*html.Node, 0, len(ns)) 519 | 520 | for _, n := range ns { 521 | cns = append(cns, cloneNode(n)) 522 | } 523 | 524 | return cns 525 | } 526 | 527 | // Deep copy a node. The new node has clones of all the original node's 528 | // children but none of its parents or siblings. 529 | func cloneNode(n *html.Node) *html.Node { 530 | nn := &html.Node{ 531 | Type: n.Type, 532 | DataAtom: n.DataAtom, 533 | Data: n.Data, 534 | Attr: make([]html.Attribute, len(n.Attr)), 535 | } 536 | 537 | copy(nn.Attr, n.Attr) 538 | for c := n.FirstChild; c != nil; c = c.NextSibling { 539 | nn.AppendChild(cloneNode(c)) 540 | } 541 | 542 | return nn 543 | } 544 | 545 | func (s *Selection) manipulateNodes(ns []*html.Node, reverse bool, 546 | f func(sn *html.Node, n *html.Node)) *Selection { 547 | 548 | lasti := s.Size() - 1 549 | 550 | // net.Html doesn't provide document fragments for insertion, so to get 551 | // things in the correct order with After() and Prepend(), the callback 552 | // needs to be called on the reverse of the nodes. 553 | if reverse { 554 | for i, j := 0, len(ns)-1; i < j; i, j = i+1, j-1 { 555 | ns[i], ns[j] = ns[j], ns[i] 556 | } 557 | } 558 | 559 | for i, sn := range s.Nodes { 560 | for _, n := range ns { 561 | if i != lasti { 562 | f(sn, cloneNode(n)) 563 | } else { 564 | if n.Parent != nil { 565 | n.Parent.RemoveChild(n) 566 | } 567 | f(sn, n) 568 | } 569 | } 570 | } 571 | 572 | return s 573 | } 574 | -------------------------------------------------------------------------------- /vendor/github.com/andybalholm/cascadia/parser.go: -------------------------------------------------------------------------------- 1 | // Package cascadia is an implementation of CSS selectors. 2 | package cascadia 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | 11 | "golang.org/x/net/html" 12 | ) 13 | 14 | // a parser for CSS selectors 15 | type parser struct { 16 | s string // the source text 17 | i int // the current position 18 | } 19 | 20 | // parseEscape parses a backslash escape. 21 | func (p *parser) parseEscape() (result string, err error) { 22 | if len(p.s) < p.i+2 || p.s[p.i] != '\\' { 23 | return "", errors.New("invalid escape sequence") 24 | } 25 | 26 | start := p.i + 1 27 | c := p.s[start] 28 | switch { 29 | case c == '\r' || c == '\n' || c == '\f': 30 | return "", errors.New("escaped line ending outside string") 31 | case hexDigit(c): 32 | // unicode escape (hex) 33 | var i int 34 | for i = start; i < p.i+6 && i < len(p.s) && hexDigit(p.s[i]); i++ { 35 | // empty 36 | } 37 | v, _ := strconv.ParseUint(p.s[start:i], 16, 21) 38 | if len(p.s) > i { 39 | switch p.s[i] { 40 | case '\r': 41 | i++ 42 | if len(p.s) > i && p.s[i] == '\n' { 43 | i++ 44 | } 45 | case ' ', '\t', '\n', '\f': 46 | i++ 47 | } 48 | } 49 | p.i = i 50 | return string(rune(v)), nil 51 | } 52 | 53 | // Return the literal character after the backslash. 54 | result = p.s[start : start+1] 55 | p.i += 2 56 | return result, nil 57 | } 58 | 59 | func hexDigit(c byte) bool { 60 | return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' 61 | } 62 | 63 | // nameStart returns whether c can be the first character of an identifier 64 | // (not counting an initial hyphen, or an escape sequence). 65 | func nameStart(c byte) bool { 66 | return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127 67 | } 68 | 69 | // nameChar returns whether c can be a character within an identifier 70 | // (not counting an escape sequence). 71 | func nameChar(c byte) bool { 72 | return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127 || 73 | c == '-' || '0' <= c && c <= '9' 74 | } 75 | 76 | // parseIdentifier parses an identifier. 77 | func (p *parser) parseIdentifier() (result string, err error) { 78 | startingDash := false 79 | if len(p.s) > p.i && p.s[p.i] == '-' { 80 | startingDash = true 81 | p.i++ 82 | } 83 | 84 | if len(p.s) <= p.i { 85 | return "", errors.New("expected identifier, found EOF instead") 86 | } 87 | 88 | if c := p.s[p.i]; !(nameStart(c) || c == '\\') { 89 | return "", fmt.Errorf("expected identifier, found %c instead", c) 90 | } 91 | 92 | result, err = p.parseName() 93 | if startingDash && err == nil { 94 | result = "-" + result 95 | } 96 | return 97 | } 98 | 99 | // parseName parses a name (which is like an identifier, but doesn't have 100 | // extra restrictions on the first character). 101 | func (p *parser) parseName() (result string, err error) { 102 | i := p.i 103 | loop: 104 | for i < len(p.s) { 105 | c := p.s[i] 106 | switch { 107 | case nameChar(c): 108 | start := i 109 | for i < len(p.s) && nameChar(p.s[i]) { 110 | i++ 111 | } 112 | result += p.s[start:i] 113 | case c == '\\': 114 | p.i = i 115 | val, err := p.parseEscape() 116 | if err != nil { 117 | return "", err 118 | } 119 | i = p.i 120 | result += val 121 | default: 122 | break loop 123 | } 124 | } 125 | 126 | if result == "" { 127 | return "", errors.New("expected name, found EOF instead") 128 | } 129 | 130 | p.i = i 131 | return result, nil 132 | } 133 | 134 | // parseString parses a single- or double-quoted string. 135 | func (p *parser) parseString() (result string, err error) { 136 | i := p.i 137 | if len(p.s) < i+2 { 138 | return "", errors.New("expected string, found EOF instead") 139 | } 140 | 141 | quote := p.s[i] 142 | i++ 143 | 144 | loop: 145 | for i < len(p.s) { 146 | switch p.s[i] { 147 | case '\\': 148 | if len(p.s) > i+1 { 149 | switch c := p.s[i+1]; c { 150 | case '\r': 151 | if len(p.s) > i+2 && p.s[i+2] == '\n' { 152 | i += 3 153 | continue loop 154 | } 155 | fallthrough 156 | case '\n', '\f': 157 | i += 2 158 | continue loop 159 | } 160 | } 161 | p.i = i 162 | val, err := p.parseEscape() 163 | if err != nil { 164 | return "", err 165 | } 166 | i = p.i 167 | result += val 168 | case quote: 169 | break loop 170 | case '\r', '\n', '\f': 171 | return "", errors.New("unexpected end of line in string") 172 | default: 173 | start := i 174 | for i < len(p.s) { 175 | if c := p.s[i]; c == quote || c == '\\' || c == '\r' || c == '\n' || c == '\f' { 176 | break 177 | } 178 | i++ 179 | } 180 | result += p.s[start:i] 181 | } 182 | } 183 | 184 | if i >= len(p.s) { 185 | return "", errors.New("EOF in string") 186 | } 187 | 188 | // Consume the final quote. 189 | i++ 190 | 191 | p.i = i 192 | return result, nil 193 | } 194 | 195 | // parseRegex parses a regular expression; the end is defined by encountering an 196 | // unmatched closing ')' or ']' which is not consumed 197 | func (p *parser) parseRegex() (rx *regexp.Regexp, err error) { 198 | i := p.i 199 | if len(p.s) < i+2 { 200 | return nil, errors.New("expected regular expression, found EOF instead") 201 | } 202 | 203 | // number of open parens or brackets; 204 | // when it becomes negative, finished parsing regex 205 | open := 0 206 | 207 | loop: 208 | for i < len(p.s) { 209 | switch p.s[i] { 210 | case '(', '[': 211 | open++ 212 | case ')', ']': 213 | open-- 214 | if open < 0 { 215 | break loop 216 | } 217 | } 218 | i++ 219 | } 220 | 221 | if i >= len(p.s) { 222 | return nil, errors.New("EOF in regular expression") 223 | } 224 | rx, err = regexp.Compile(p.s[p.i:i]) 225 | p.i = i 226 | return rx, err 227 | } 228 | 229 | // skipWhitespace consumes whitespace characters and comments. 230 | // It returns true if there was actually anything to skip. 231 | func (p *parser) skipWhitespace() bool { 232 | i := p.i 233 | for i < len(p.s) { 234 | switch p.s[i] { 235 | case ' ', '\t', '\r', '\n', '\f': 236 | i++ 237 | continue 238 | case '/': 239 | if strings.HasPrefix(p.s[i:], "/*") { 240 | end := strings.Index(p.s[i+len("/*"):], "*/") 241 | if end != -1 { 242 | i += end + len("/**/") 243 | continue 244 | } 245 | } 246 | } 247 | break 248 | } 249 | 250 | if i > p.i { 251 | p.i = i 252 | return true 253 | } 254 | 255 | return false 256 | } 257 | 258 | // consumeParenthesis consumes an opening parenthesis and any following 259 | // whitespace. It returns true if there was actually a parenthesis to skip. 260 | func (p *parser) consumeParenthesis() bool { 261 | if p.i < len(p.s) && p.s[p.i] == '(' { 262 | p.i++ 263 | p.skipWhitespace() 264 | return true 265 | } 266 | return false 267 | } 268 | 269 | // consumeClosingParenthesis consumes a closing parenthesis and any preceding 270 | // whitespace. It returns true if there was actually a parenthesis to skip. 271 | func (p *parser) consumeClosingParenthesis() bool { 272 | i := p.i 273 | p.skipWhitespace() 274 | if p.i < len(p.s) && p.s[p.i] == ')' { 275 | p.i++ 276 | return true 277 | } 278 | p.i = i 279 | return false 280 | } 281 | 282 | // parseTypeSelector parses a type selector (one that matches by tag name). 283 | func (p *parser) parseTypeSelector() (result Selector, err error) { 284 | tag, err := p.parseIdentifier() 285 | if err != nil { 286 | return nil, err 287 | } 288 | 289 | return typeSelector(tag), nil 290 | } 291 | 292 | // parseIDSelector parses a selector that matches by id attribute. 293 | func (p *parser) parseIDSelector() (Selector, error) { 294 | if p.i >= len(p.s) { 295 | return nil, fmt.Errorf("expected id selector (#id), found EOF instead") 296 | } 297 | if p.s[p.i] != '#' { 298 | return nil, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i]) 299 | } 300 | 301 | p.i++ 302 | id, err := p.parseName() 303 | if err != nil { 304 | return nil, err 305 | } 306 | 307 | return attributeEqualsSelector("id", id), nil 308 | } 309 | 310 | // parseClassSelector parses a selector that matches by class attribute. 311 | func (p *parser) parseClassSelector() (Selector, error) { 312 | if p.i >= len(p.s) { 313 | return nil, fmt.Errorf("expected class selector (.class), found EOF instead") 314 | } 315 | if p.s[p.i] != '.' { 316 | return nil, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i]) 317 | } 318 | 319 | p.i++ 320 | class, err := p.parseIdentifier() 321 | if err != nil { 322 | return nil, err 323 | } 324 | 325 | return attributeIncludesSelector("class", class), nil 326 | } 327 | 328 | // parseAttributeSelector parses a selector that matches by attribute value. 329 | func (p *parser) parseAttributeSelector() (Selector, error) { 330 | if p.i >= len(p.s) { 331 | return nil, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead") 332 | } 333 | if p.s[p.i] != '[' { 334 | return nil, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i]) 335 | } 336 | 337 | p.i++ 338 | p.skipWhitespace() 339 | key, err := p.parseIdentifier() 340 | if err != nil { 341 | return nil, err 342 | } 343 | 344 | p.skipWhitespace() 345 | if p.i >= len(p.s) { 346 | return nil, errors.New("unexpected EOF in attribute selector") 347 | } 348 | 349 | if p.s[p.i] == ']' { 350 | p.i++ 351 | return attributeExistsSelector(key), nil 352 | } 353 | 354 | if p.i+2 >= len(p.s) { 355 | return nil, errors.New("unexpected EOF in attribute selector") 356 | } 357 | 358 | op := p.s[p.i : p.i+2] 359 | if op[0] == '=' { 360 | op = "=" 361 | } else if op[1] != '=' { 362 | return nil, fmt.Errorf(`expected equality operator, found "%s" instead`, op) 363 | } 364 | p.i += len(op) 365 | 366 | p.skipWhitespace() 367 | if p.i >= len(p.s) { 368 | return nil, errors.New("unexpected EOF in attribute selector") 369 | } 370 | var val string 371 | var rx *regexp.Regexp 372 | if op == "#=" { 373 | rx, err = p.parseRegex() 374 | } else { 375 | switch p.s[p.i] { 376 | case '\'', '"': 377 | val, err = p.parseString() 378 | default: 379 | val, err = p.parseIdentifier() 380 | } 381 | } 382 | if err != nil { 383 | return nil, err 384 | } 385 | 386 | p.skipWhitespace() 387 | if p.i >= len(p.s) { 388 | return nil, errors.New("unexpected EOF in attribute selector") 389 | } 390 | if p.s[p.i] != ']' { 391 | return nil, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i]) 392 | } 393 | p.i++ 394 | 395 | switch op { 396 | case "=": 397 | return attributeEqualsSelector(key, val), nil 398 | case "!=": 399 | return attributeNotEqualSelector(key, val), nil 400 | case "~=": 401 | return attributeIncludesSelector(key, val), nil 402 | case "|=": 403 | return attributeDashmatchSelector(key, val), nil 404 | case "^=": 405 | return attributePrefixSelector(key, val), nil 406 | case "$=": 407 | return attributeSuffixSelector(key, val), nil 408 | case "*=": 409 | return attributeSubstringSelector(key, val), nil 410 | case "#=": 411 | return attributeRegexSelector(key, rx), nil 412 | } 413 | 414 | return nil, fmt.Errorf("attribute operator %q is not supported", op) 415 | } 416 | 417 | var errExpectedParenthesis = errors.New("expected '(' but didn't find it") 418 | var errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it") 419 | var errUnmatchedParenthesis = errors.New("unmatched '('") 420 | 421 | // parsePseudoclassSelector parses a pseudoclass selector like :not(p). 422 | func (p *parser) parsePseudoclassSelector() (Selector, error) { 423 | if p.i >= len(p.s) { 424 | return nil, fmt.Errorf("expected pseudoclass selector (:pseudoclass), found EOF instead") 425 | } 426 | if p.s[p.i] != ':' { 427 | return nil, fmt.Errorf("expected attribute selector (:pseudoclass), found '%c' instead", p.s[p.i]) 428 | } 429 | 430 | p.i++ 431 | name, err := p.parseIdentifier() 432 | if err != nil { 433 | return nil, err 434 | } 435 | name = toLowerASCII(name) 436 | 437 | switch name { 438 | case "not", "has", "haschild": 439 | if !p.consumeParenthesis() { 440 | return nil, errExpectedParenthesis 441 | } 442 | sel, parseErr := p.parseSelectorGroup() 443 | if parseErr != nil { 444 | return nil, parseErr 445 | } 446 | if !p.consumeClosingParenthesis() { 447 | return nil, errExpectedClosingParenthesis 448 | } 449 | 450 | switch name { 451 | case "not": 452 | return negatedSelector(sel), nil 453 | case "has": 454 | return hasDescendantSelector(sel), nil 455 | case "haschild": 456 | return hasChildSelector(sel), nil 457 | } 458 | 459 | case "contains", "containsown": 460 | if !p.consumeParenthesis() { 461 | return nil, errExpectedParenthesis 462 | } 463 | if p.i == len(p.s) { 464 | return nil, errUnmatchedParenthesis 465 | } 466 | var val string 467 | switch p.s[p.i] { 468 | case '\'', '"': 469 | val, err = p.parseString() 470 | default: 471 | val, err = p.parseIdentifier() 472 | } 473 | if err != nil { 474 | return nil, err 475 | } 476 | val = strings.ToLower(val) 477 | p.skipWhitespace() 478 | if p.i >= len(p.s) { 479 | return nil, errors.New("unexpected EOF in pseudo selector") 480 | } 481 | if !p.consumeClosingParenthesis() { 482 | return nil, errExpectedClosingParenthesis 483 | } 484 | 485 | switch name { 486 | case "contains": 487 | return textSubstrSelector(val), nil 488 | case "containsown": 489 | return ownTextSubstrSelector(val), nil 490 | } 491 | 492 | case "matches", "matchesown": 493 | if !p.consumeParenthesis() { 494 | return nil, errExpectedParenthesis 495 | } 496 | rx, err := p.parseRegex() 497 | if err != nil { 498 | return nil, err 499 | } 500 | if p.i >= len(p.s) { 501 | return nil, errors.New("unexpected EOF in pseudo selector") 502 | } 503 | if !p.consumeClosingParenthesis() { 504 | return nil, errExpectedClosingParenthesis 505 | } 506 | 507 | switch name { 508 | case "matches": 509 | return textRegexSelector(rx), nil 510 | case "matchesown": 511 | return ownTextRegexSelector(rx), nil 512 | } 513 | 514 | case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type": 515 | if !p.consumeParenthesis() { 516 | return nil, errExpectedParenthesis 517 | } 518 | a, b, err := p.parseNth() 519 | if err != nil { 520 | return nil, err 521 | } 522 | if !p.consumeClosingParenthesis() { 523 | return nil, errExpectedClosingParenthesis 524 | } 525 | if a == 0 { 526 | switch name { 527 | case "nth-child": 528 | return simpleNthChildSelector(b, false), nil 529 | case "nth-of-type": 530 | return simpleNthChildSelector(b, true), nil 531 | case "nth-last-child": 532 | return simpleNthLastChildSelector(b, false), nil 533 | case "nth-last-of-type": 534 | return simpleNthLastChildSelector(b, true), nil 535 | } 536 | } 537 | return nthChildSelector(a, b, 538 | name == "nth-last-child" || name == "nth-last-of-type", 539 | name == "nth-of-type" || name == "nth-last-of-type"), 540 | nil 541 | 542 | case "first-child": 543 | return simpleNthChildSelector(1, false), nil 544 | case "last-child": 545 | return simpleNthLastChildSelector(1, false), nil 546 | case "first-of-type": 547 | return simpleNthChildSelector(1, true), nil 548 | case "last-of-type": 549 | return simpleNthLastChildSelector(1, true), nil 550 | case "only-child": 551 | return onlyChildSelector(false), nil 552 | case "only-of-type": 553 | return onlyChildSelector(true), nil 554 | case "input": 555 | return inputSelector, nil 556 | case "empty": 557 | return emptyElementSelector, nil 558 | case "root": 559 | return rootSelector, nil 560 | } 561 | 562 | return nil, fmt.Errorf("unknown pseudoclass :%s", name) 563 | } 564 | 565 | // parseInteger parses a decimal integer. 566 | func (p *parser) parseInteger() (int, error) { 567 | i := p.i 568 | start := i 569 | for i < len(p.s) && '0' <= p.s[i] && p.s[i] <= '9' { 570 | i++ 571 | } 572 | if i == start { 573 | return 0, errors.New("expected integer, but didn't find it") 574 | } 575 | p.i = i 576 | 577 | val, err := strconv.Atoi(p.s[start:i]) 578 | if err != nil { 579 | return 0, err 580 | } 581 | 582 | return val, nil 583 | } 584 | 585 | // parseNth parses the argument for :nth-child (normally of the form an+b). 586 | func (p *parser) parseNth() (a, b int, err error) { 587 | // initial state 588 | if p.i >= len(p.s) { 589 | goto eof 590 | } 591 | switch p.s[p.i] { 592 | case '-': 593 | p.i++ 594 | goto negativeA 595 | case '+': 596 | p.i++ 597 | goto positiveA 598 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 599 | goto positiveA 600 | case 'n', 'N': 601 | a = 1 602 | p.i++ 603 | goto readN 604 | case 'o', 'O', 'e', 'E': 605 | id, nameErr := p.parseName() 606 | if nameErr != nil { 607 | return 0, 0, nameErr 608 | } 609 | id = toLowerASCII(id) 610 | if id == "odd" { 611 | return 2, 1, nil 612 | } 613 | if id == "even" { 614 | return 2, 0, nil 615 | } 616 | return 0, 0, fmt.Errorf("expected 'odd' or 'even', but found '%s' instead", id) 617 | default: 618 | goto invalid 619 | } 620 | 621 | positiveA: 622 | if p.i >= len(p.s) { 623 | goto eof 624 | } 625 | switch p.s[p.i] { 626 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 627 | a, err = p.parseInteger() 628 | if err != nil { 629 | return 0, 0, err 630 | } 631 | goto readA 632 | case 'n', 'N': 633 | a = 1 634 | p.i++ 635 | goto readN 636 | default: 637 | goto invalid 638 | } 639 | 640 | negativeA: 641 | if p.i >= len(p.s) { 642 | goto eof 643 | } 644 | switch p.s[p.i] { 645 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 646 | a, err = p.parseInteger() 647 | if err != nil { 648 | return 0, 0, err 649 | } 650 | a = -a 651 | goto readA 652 | case 'n', 'N': 653 | a = -1 654 | p.i++ 655 | goto readN 656 | default: 657 | goto invalid 658 | } 659 | 660 | readA: 661 | if p.i >= len(p.s) { 662 | goto eof 663 | } 664 | switch p.s[p.i] { 665 | case 'n', 'N': 666 | p.i++ 667 | goto readN 668 | default: 669 | // The number we read as a is actually b. 670 | return 0, a, nil 671 | } 672 | 673 | readN: 674 | p.skipWhitespace() 675 | if p.i >= len(p.s) { 676 | goto eof 677 | } 678 | switch p.s[p.i] { 679 | case '+': 680 | p.i++ 681 | p.skipWhitespace() 682 | b, err = p.parseInteger() 683 | if err != nil { 684 | return 0, 0, err 685 | } 686 | return a, b, nil 687 | case '-': 688 | p.i++ 689 | p.skipWhitespace() 690 | b, err = p.parseInteger() 691 | if err != nil { 692 | return 0, 0, err 693 | } 694 | return a, -b, nil 695 | default: 696 | return a, 0, nil 697 | } 698 | 699 | eof: 700 | return 0, 0, errors.New("unexpected EOF while attempting to parse expression of form an+b") 701 | 702 | invalid: 703 | return 0, 0, errors.New("unexpected character while attempting to parse expression of form an+b") 704 | } 705 | 706 | // parseSimpleSelectorSequence parses a selector sequence that applies to 707 | // a single element. 708 | func (p *parser) parseSimpleSelectorSequence() (Selector, error) { 709 | var result Selector 710 | 711 | if p.i >= len(p.s) { 712 | return nil, errors.New("expected selector, found EOF instead") 713 | } 714 | 715 | switch p.s[p.i] { 716 | case '*': 717 | // It's the universal selector. Just skip over it, since it doesn't affect the meaning. 718 | p.i++ 719 | case '#', '.', '[', ':': 720 | // There's no type selector. Wait to process the other till the main loop. 721 | default: 722 | r, err := p.parseTypeSelector() 723 | if err != nil { 724 | return nil, err 725 | } 726 | result = r 727 | } 728 | 729 | loop: 730 | for p.i < len(p.s) { 731 | var ns Selector 732 | var err error 733 | switch p.s[p.i] { 734 | case '#': 735 | ns, err = p.parseIDSelector() 736 | case '.': 737 | ns, err = p.parseClassSelector() 738 | case '[': 739 | ns, err = p.parseAttributeSelector() 740 | case ':': 741 | ns, err = p.parsePseudoclassSelector() 742 | default: 743 | break loop 744 | } 745 | if err != nil { 746 | return nil, err 747 | } 748 | if result == nil { 749 | result = ns 750 | } else { 751 | result = intersectionSelector(result, ns) 752 | } 753 | } 754 | 755 | if result == nil { 756 | result = func(n *html.Node) bool { 757 | return n.Type == html.ElementNode 758 | } 759 | } 760 | 761 | return result, nil 762 | } 763 | 764 | // parseSelector parses a selector that may include combinators. 765 | func (p *parser) parseSelector() (result Selector, err error) { 766 | p.skipWhitespace() 767 | result, err = p.parseSimpleSelectorSequence() 768 | if err != nil { 769 | return 770 | } 771 | 772 | for { 773 | var combinator byte 774 | if p.skipWhitespace() { 775 | combinator = ' ' 776 | } 777 | if p.i >= len(p.s) { 778 | return 779 | } 780 | 781 | switch p.s[p.i] { 782 | case '+', '>', '~': 783 | combinator = p.s[p.i] 784 | p.i++ 785 | p.skipWhitespace() 786 | case ',', ')': 787 | // These characters can't begin a selector, but they can legally occur after one. 788 | return 789 | } 790 | 791 | if combinator == 0 { 792 | return 793 | } 794 | 795 | c, err := p.parseSimpleSelectorSequence() 796 | if err != nil { 797 | return nil, err 798 | } 799 | 800 | switch combinator { 801 | case ' ': 802 | result = descendantSelector(result, c) 803 | case '>': 804 | result = childSelector(result, c) 805 | case '+': 806 | result = siblingSelector(result, c, true) 807 | case '~': 808 | result = siblingSelector(result, c, false) 809 | } 810 | } 811 | 812 | panic("unreachable") 813 | } 814 | 815 | // parseSelectorGroup parses a group of selectors, separated by commas. 816 | func (p *parser) parseSelectorGroup() (result Selector, err error) { 817 | result, err = p.parseSelector() 818 | if err != nil { 819 | return 820 | } 821 | 822 | for p.i < len(p.s) { 823 | if p.s[p.i] != ',' { 824 | return result, nil 825 | } 826 | p.i++ 827 | c, err := p.parseSelector() 828 | if err != nil { 829 | return nil, err 830 | } 831 | result = unionSelector(result, c) 832 | } 833 | 834 | return 835 | } 836 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/atom/table.go: -------------------------------------------------------------------------------- 1 | // generated by go run gen.go; DO NOT EDIT 2 | 3 | package atom 4 | 5 | const ( 6 | A Atom = 0x1 7 | Abbr Atom = 0x4 8 | Accept Atom = 0x2106 9 | AcceptCharset Atom = 0x210e 10 | Accesskey Atom = 0x3309 11 | Action Atom = 0x1f606 12 | Address Atom = 0x4f307 13 | Align Atom = 0x1105 14 | Alt Atom = 0x4503 15 | Annotation Atom = 0x1670a 16 | AnnotationXml Atom = 0x1670e 17 | Applet Atom = 0x2b306 18 | Area Atom = 0x2fa04 19 | Article Atom = 0x38807 20 | Aside Atom = 0x8305 21 | Async Atom = 0x7b05 22 | Audio Atom = 0xa605 23 | Autocomplete Atom = 0x1fc0c 24 | Autofocus Atom = 0xb309 25 | Autoplay Atom = 0xce08 26 | B Atom = 0x101 27 | Base Atom = 0xd604 28 | Basefont Atom = 0xd608 29 | Bdi Atom = 0x1a03 30 | Bdo Atom = 0xe703 31 | Bgsound Atom = 0x11807 32 | Big Atom = 0x12403 33 | Blink Atom = 0x12705 34 | Blockquote Atom = 0x12c0a 35 | Body Atom = 0x2f04 36 | Br Atom = 0x202 37 | Button Atom = 0x13606 38 | Canvas Atom = 0x7f06 39 | Caption Atom = 0x1bb07 40 | Center Atom = 0x5b506 41 | Challenge Atom = 0x21f09 42 | Charset Atom = 0x2807 43 | Checked Atom = 0x32807 44 | Cite Atom = 0x3c804 45 | Class Atom = 0x4de05 46 | Code Atom = 0x14904 47 | Col Atom = 0x15003 48 | Colgroup Atom = 0x15008 49 | Color Atom = 0x15d05 50 | Cols Atom = 0x16204 51 | Colspan Atom = 0x16207 52 | Command Atom = 0x17507 53 | Content Atom = 0x42307 54 | Contenteditable Atom = 0x4230f 55 | Contextmenu Atom = 0x3310b 56 | Controls Atom = 0x18808 57 | Coords Atom = 0x19406 58 | Crossorigin Atom = 0x19f0b 59 | Data Atom = 0x44a04 60 | Datalist Atom = 0x44a08 61 | Datetime Atom = 0x23c08 62 | Dd Atom = 0x26702 63 | Default Atom = 0x8607 64 | Defer Atom = 0x14b05 65 | Del Atom = 0x3ef03 66 | Desc Atom = 0x4db04 67 | Details Atom = 0x4807 68 | Dfn Atom = 0x6103 69 | Dialog Atom = 0x1b06 70 | Dir Atom = 0x6903 71 | Dirname Atom = 0x6907 72 | Disabled Atom = 0x10c08 73 | Div Atom = 0x11303 74 | Dl Atom = 0x11e02 75 | Download Atom = 0x40008 76 | Draggable Atom = 0x17b09 77 | Dropzone Atom = 0x39108 78 | Dt Atom = 0x50902 79 | Em Atom = 0x6502 80 | Embed Atom = 0x6505 81 | Enctype Atom = 0x21107 82 | Face Atom = 0x5b304 83 | Fieldset Atom = 0x1b008 84 | Figcaption Atom = 0x1b80a 85 | Figure Atom = 0x1cc06 86 | Font Atom = 0xda04 87 | Footer Atom = 0x8d06 88 | For Atom = 0x1d803 89 | ForeignObject Atom = 0x1d80d 90 | Foreignobject Atom = 0x1e50d 91 | Form Atom = 0x1f204 92 | Formaction Atom = 0x1f20a 93 | Formenctype Atom = 0x20d0b 94 | Formmethod Atom = 0x2280a 95 | Formnovalidate Atom = 0x2320e 96 | Formtarget Atom = 0x2470a 97 | Frame Atom = 0x9a05 98 | Frameset Atom = 0x9a08 99 | H1 Atom = 0x26e02 100 | H2 Atom = 0x29402 101 | H3 Atom = 0x2a702 102 | H4 Atom = 0x2e902 103 | H5 Atom = 0x2f302 104 | H6 Atom = 0x50b02 105 | Head Atom = 0x2d504 106 | Header Atom = 0x2d506 107 | Headers Atom = 0x2d507 108 | Height Atom = 0x25106 109 | Hgroup Atom = 0x25906 110 | Hidden Atom = 0x26506 111 | High Atom = 0x26b04 112 | Hr Atom = 0x27002 113 | Href Atom = 0x27004 114 | Hreflang Atom = 0x27008 115 | Html Atom = 0x25504 116 | HttpEquiv Atom = 0x2780a 117 | I Atom = 0x601 118 | Icon Atom = 0x42204 119 | Id Atom = 0x8502 120 | Iframe Atom = 0x29606 121 | Image Atom = 0x29c05 122 | Img Atom = 0x2a103 123 | Input Atom = 0x3e805 124 | Inputmode Atom = 0x3e809 125 | Ins Atom = 0x1a803 126 | Isindex Atom = 0x2a907 127 | Ismap Atom = 0x2b005 128 | Itemid Atom = 0x33c06 129 | Itemprop Atom = 0x3c908 130 | Itemref Atom = 0x5ad07 131 | Itemscope Atom = 0x2b909 132 | Itemtype Atom = 0x2c308 133 | Kbd Atom = 0x1903 134 | Keygen Atom = 0x3906 135 | Keytype Atom = 0x53707 136 | Kind Atom = 0x10904 137 | Label Atom = 0xf005 138 | Lang Atom = 0x27404 139 | Legend Atom = 0x18206 140 | Li Atom = 0x1202 141 | Link Atom = 0x12804 142 | List Atom = 0x44e04 143 | Listing Atom = 0x44e07 144 | Loop Atom = 0xf404 145 | Low Atom = 0x11f03 146 | Malignmark Atom = 0x100a 147 | Manifest Atom = 0x5f108 148 | Map Atom = 0x2b203 149 | Mark Atom = 0x1604 150 | Marquee Atom = 0x2cb07 151 | Math Atom = 0x2d204 152 | Max Atom = 0x2e103 153 | Maxlength Atom = 0x2e109 154 | Media Atom = 0x6e05 155 | Mediagroup Atom = 0x6e0a 156 | Menu Atom = 0x33804 157 | Menuitem Atom = 0x33808 158 | Meta Atom = 0x45d04 159 | Meter Atom = 0x24205 160 | Method Atom = 0x22c06 161 | Mglyph Atom = 0x2a206 162 | Mi Atom = 0x2eb02 163 | Min Atom = 0x2eb03 164 | Minlength Atom = 0x2eb09 165 | Mn Atom = 0x23502 166 | Mo Atom = 0x3ed02 167 | Ms Atom = 0x2bc02 168 | Mtext Atom = 0x2f505 169 | Multiple Atom = 0x30308 170 | Muted Atom = 0x30b05 171 | Name Atom = 0x6c04 172 | Nav Atom = 0x3e03 173 | Nobr Atom = 0x5704 174 | Noembed Atom = 0x6307 175 | Noframes Atom = 0x9808 176 | Noscript Atom = 0x3d208 177 | Novalidate Atom = 0x2360a 178 | Object Atom = 0x1ec06 179 | Ol Atom = 0xc902 180 | Onabort Atom = 0x13a07 181 | Onafterprint Atom = 0x1c00c 182 | Onautocomplete Atom = 0x1fa0e 183 | Onautocompleteerror Atom = 0x1fa13 184 | Onbeforeprint Atom = 0x6040d 185 | Onbeforeunload Atom = 0x4e70e 186 | Onblur Atom = 0xaa06 187 | Oncancel Atom = 0xe908 188 | Oncanplay Atom = 0x28509 189 | Oncanplaythrough Atom = 0x28510 190 | Onchange Atom = 0x3a708 191 | Onclick Atom = 0x31007 192 | Onclose Atom = 0x31707 193 | Oncontextmenu Atom = 0x32f0d 194 | Oncuechange Atom = 0x3420b 195 | Ondblclick Atom = 0x34d0a 196 | Ondrag Atom = 0x35706 197 | Ondragend Atom = 0x35709 198 | Ondragenter Atom = 0x3600b 199 | Ondragleave Atom = 0x36b0b 200 | Ondragover Atom = 0x3760a 201 | Ondragstart Atom = 0x3800b 202 | Ondrop Atom = 0x38f06 203 | Ondurationchange Atom = 0x39f10 204 | Onemptied Atom = 0x39609 205 | Onended Atom = 0x3af07 206 | Onerror Atom = 0x3b607 207 | Onfocus Atom = 0x3bd07 208 | Onhashchange Atom = 0x3da0c 209 | Oninput Atom = 0x3e607 210 | Oninvalid Atom = 0x3f209 211 | Onkeydown Atom = 0x3fb09 212 | Onkeypress Atom = 0x4080a 213 | Onkeyup Atom = 0x41807 214 | Onlanguagechange Atom = 0x43210 215 | Onload Atom = 0x44206 216 | Onloadeddata Atom = 0x4420c 217 | Onloadedmetadata Atom = 0x45510 218 | Onloadstart Atom = 0x46b0b 219 | Onmessage Atom = 0x47609 220 | Onmousedown Atom = 0x47f0b 221 | Onmousemove Atom = 0x48a0b 222 | Onmouseout Atom = 0x4950a 223 | Onmouseover Atom = 0x4a20b 224 | Onmouseup Atom = 0x4ad09 225 | Onmousewheel Atom = 0x4b60c 226 | Onoffline Atom = 0x4c209 227 | Ononline Atom = 0x4cb08 228 | Onpagehide Atom = 0x4d30a 229 | Onpageshow Atom = 0x4fe0a 230 | Onpause Atom = 0x50d07 231 | Onplay Atom = 0x51706 232 | Onplaying Atom = 0x51709 233 | Onpopstate Atom = 0x5200a 234 | Onprogress Atom = 0x52a0a 235 | Onratechange Atom = 0x53e0c 236 | Onreset Atom = 0x54a07 237 | Onresize Atom = 0x55108 238 | Onscroll Atom = 0x55f08 239 | Onseeked Atom = 0x56708 240 | Onseeking Atom = 0x56f09 241 | Onselect Atom = 0x57808 242 | Onshow Atom = 0x58206 243 | Onsort Atom = 0x58b06 244 | Onstalled Atom = 0x59509 245 | Onstorage Atom = 0x59e09 246 | Onsubmit Atom = 0x5a708 247 | Onsuspend Atom = 0x5bb09 248 | Ontimeupdate Atom = 0xdb0c 249 | Ontoggle Atom = 0x5c408 250 | Onunload Atom = 0x5cc08 251 | Onvolumechange Atom = 0x5d40e 252 | Onwaiting Atom = 0x5e209 253 | Open Atom = 0x3cf04 254 | Optgroup Atom = 0xf608 255 | Optimum Atom = 0x5eb07 256 | Option Atom = 0x60006 257 | Output Atom = 0x49c06 258 | P Atom = 0xc01 259 | Param Atom = 0xc05 260 | Pattern Atom = 0x5107 261 | Ping Atom = 0x7704 262 | Placeholder Atom = 0xc30b 263 | Plaintext Atom = 0xfd09 264 | Poster Atom = 0x15706 265 | Pre Atom = 0x25e03 266 | Preload Atom = 0x25e07 267 | Progress Atom = 0x52c08 268 | Prompt Atom = 0x5fa06 269 | Public Atom = 0x41e06 270 | Q Atom = 0x13101 271 | Radiogroup Atom = 0x30a 272 | Readonly Atom = 0x2fb08 273 | Rel Atom = 0x25f03 274 | Required Atom = 0x1d008 275 | Reversed Atom = 0x5a08 276 | Rows Atom = 0x9204 277 | Rowspan Atom = 0x9207 278 | Rp Atom = 0x1c602 279 | Rt Atom = 0x13f02 280 | Ruby Atom = 0xaf04 281 | S Atom = 0x2c01 282 | Samp Atom = 0x4e04 283 | Sandbox Atom = 0xbb07 284 | Scope Atom = 0x2bd05 285 | Scoped Atom = 0x2bd06 286 | Script Atom = 0x3d406 287 | Seamless Atom = 0x31c08 288 | Section Atom = 0x4e207 289 | Select Atom = 0x57a06 290 | Selected Atom = 0x57a08 291 | Shape Atom = 0x4f905 292 | Size Atom = 0x55504 293 | Sizes Atom = 0x55505 294 | Small Atom = 0x18f05 295 | Sortable Atom = 0x58d08 296 | Sorted Atom = 0x19906 297 | Source Atom = 0x1aa06 298 | Spacer Atom = 0x2db06 299 | Span Atom = 0x9504 300 | Spellcheck Atom = 0x3230a 301 | Src Atom = 0x3c303 302 | Srcdoc Atom = 0x3c306 303 | Srclang Atom = 0x41107 304 | Start Atom = 0x38605 305 | Step Atom = 0x5f704 306 | Strike Atom = 0x53306 307 | Strong Atom = 0x55906 308 | Style Atom = 0x61105 309 | Sub Atom = 0x5a903 310 | Summary Atom = 0x61607 311 | Sup Atom = 0x61d03 312 | Svg Atom = 0x62003 313 | System Atom = 0x62306 314 | Tabindex Atom = 0x46308 315 | Table Atom = 0x42d05 316 | Target Atom = 0x24b06 317 | Tbody Atom = 0x2e05 318 | Td Atom = 0x4702 319 | Template Atom = 0x62608 320 | Textarea Atom = 0x2f608 321 | Tfoot Atom = 0x8c05 322 | Th Atom = 0x22e02 323 | Thead Atom = 0x2d405 324 | Time Atom = 0xdd04 325 | Title Atom = 0xa105 326 | Tr Atom = 0x10502 327 | Track Atom = 0x10505 328 | Translate Atom = 0x14009 329 | Tt Atom = 0x5302 330 | Type Atom = 0x21404 331 | Typemustmatch Atom = 0x2140d 332 | U Atom = 0xb01 333 | Ul Atom = 0x8a02 334 | Usemap Atom = 0x51106 335 | Value Atom = 0x4005 336 | Var Atom = 0x11503 337 | Video Atom = 0x28105 338 | Wbr Atom = 0x12103 339 | Width Atom = 0x50705 340 | Wrap Atom = 0x58704 341 | Xmp Atom = 0xc103 342 | ) 343 | 344 | const hash0 = 0xc17da63e 345 | 346 | const maxAtomLen = 19 347 | 348 | var table = [1 << 9]Atom{ 349 | 0x1: 0x48a0b, // onmousemove 350 | 0x2: 0x5e209, // onwaiting 351 | 0x3: 0x1fa13, // onautocompleteerror 352 | 0x4: 0x5fa06, // prompt 353 | 0x7: 0x5eb07, // optimum 354 | 0x8: 0x1604, // mark 355 | 0xa: 0x5ad07, // itemref 356 | 0xb: 0x4fe0a, // onpageshow 357 | 0xc: 0x57a06, // select 358 | 0xd: 0x17b09, // draggable 359 | 0xe: 0x3e03, // nav 360 | 0xf: 0x17507, // command 361 | 0x11: 0xb01, // u 362 | 0x14: 0x2d507, // headers 363 | 0x15: 0x44a08, // datalist 364 | 0x17: 0x4e04, // samp 365 | 0x1a: 0x3fb09, // onkeydown 366 | 0x1b: 0x55f08, // onscroll 367 | 0x1c: 0x15003, // col 368 | 0x20: 0x3c908, // itemprop 369 | 0x21: 0x2780a, // http-equiv 370 | 0x22: 0x61d03, // sup 371 | 0x24: 0x1d008, // required 372 | 0x2b: 0x25e07, // preload 373 | 0x2c: 0x6040d, // onbeforeprint 374 | 0x2d: 0x3600b, // ondragenter 375 | 0x2e: 0x50902, // dt 376 | 0x2f: 0x5a708, // onsubmit 377 | 0x30: 0x27002, // hr 378 | 0x31: 0x32f0d, // oncontextmenu 379 | 0x33: 0x29c05, // image 380 | 0x34: 0x50d07, // onpause 381 | 0x35: 0x25906, // hgroup 382 | 0x36: 0x7704, // ping 383 | 0x37: 0x57808, // onselect 384 | 0x3a: 0x11303, // div 385 | 0x3b: 0x1fa0e, // onautocomplete 386 | 0x40: 0x2eb02, // mi 387 | 0x41: 0x31c08, // seamless 388 | 0x42: 0x2807, // charset 389 | 0x43: 0x8502, // id 390 | 0x44: 0x5200a, // onpopstate 391 | 0x45: 0x3ef03, // del 392 | 0x46: 0x2cb07, // marquee 393 | 0x47: 0x3309, // accesskey 394 | 0x49: 0x8d06, // footer 395 | 0x4a: 0x44e04, // list 396 | 0x4b: 0x2b005, // ismap 397 | 0x51: 0x33804, // menu 398 | 0x52: 0x2f04, // body 399 | 0x55: 0x9a08, // frameset 400 | 0x56: 0x54a07, // onreset 401 | 0x57: 0x12705, // blink 402 | 0x58: 0xa105, // title 403 | 0x59: 0x38807, // article 404 | 0x5b: 0x22e02, // th 405 | 0x5d: 0x13101, // q 406 | 0x5e: 0x3cf04, // open 407 | 0x5f: 0x2fa04, // area 408 | 0x61: 0x44206, // onload 409 | 0x62: 0xda04, // font 410 | 0x63: 0xd604, // base 411 | 0x64: 0x16207, // colspan 412 | 0x65: 0x53707, // keytype 413 | 0x66: 0x11e02, // dl 414 | 0x68: 0x1b008, // fieldset 415 | 0x6a: 0x2eb03, // min 416 | 0x6b: 0x11503, // var 417 | 0x6f: 0x2d506, // header 418 | 0x70: 0x13f02, // rt 419 | 0x71: 0x15008, // colgroup 420 | 0x72: 0x23502, // mn 421 | 0x74: 0x13a07, // onabort 422 | 0x75: 0x3906, // keygen 423 | 0x76: 0x4c209, // onoffline 424 | 0x77: 0x21f09, // challenge 425 | 0x78: 0x2b203, // map 426 | 0x7a: 0x2e902, // h4 427 | 0x7b: 0x3b607, // onerror 428 | 0x7c: 0x2e109, // maxlength 429 | 0x7d: 0x2f505, // mtext 430 | 0x7e: 0xbb07, // sandbox 431 | 0x7f: 0x58b06, // onsort 432 | 0x80: 0x100a, // malignmark 433 | 0x81: 0x45d04, // meta 434 | 0x82: 0x7b05, // async 435 | 0x83: 0x2a702, // h3 436 | 0x84: 0x26702, // dd 437 | 0x85: 0x27004, // href 438 | 0x86: 0x6e0a, // mediagroup 439 | 0x87: 0x19406, // coords 440 | 0x88: 0x41107, // srclang 441 | 0x89: 0x34d0a, // ondblclick 442 | 0x8a: 0x4005, // value 443 | 0x8c: 0xe908, // oncancel 444 | 0x8e: 0x3230a, // spellcheck 445 | 0x8f: 0x9a05, // frame 446 | 0x91: 0x12403, // big 447 | 0x94: 0x1f606, // action 448 | 0x95: 0x6903, // dir 449 | 0x97: 0x2fb08, // readonly 450 | 0x99: 0x42d05, // table 451 | 0x9a: 0x61607, // summary 452 | 0x9b: 0x12103, // wbr 453 | 0x9c: 0x30a, // radiogroup 454 | 0x9d: 0x6c04, // name 455 | 0x9f: 0x62306, // system 456 | 0xa1: 0x15d05, // color 457 | 0xa2: 0x7f06, // canvas 458 | 0xa3: 0x25504, // html 459 | 0xa5: 0x56f09, // onseeking 460 | 0xac: 0x4f905, // shape 461 | 0xad: 0x25f03, // rel 462 | 0xae: 0x28510, // oncanplaythrough 463 | 0xaf: 0x3760a, // ondragover 464 | 0xb0: 0x62608, // template 465 | 0xb1: 0x1d80d, // foreignObject 466 | 0xb3: 0x9204, // rows 467 | 0xb6: 0x44e07, // listing 468 | 0xb7: 0x49c06, // output 469 | 0xb9: 0x3310b, // contextmenu 470 | 0xbb: 0x11f03, // low 471 | 0xbc: 0x1c602, // rp 472 | 0xbd: 0x5bb09, // onsuspend 473 | 0xbe: 0x13606, // button 474 | 0xbf: 0x4db04, // desc 475 | 0xc1: 0x4e207, // section 476 | 0xc2: 0x52a0a, // onprogress 477 | 0xc3: 0x59e09, // onstorage 478 | 0xc4: 0x2d204, // math 479 | 0xc5: 0x4503, // alt 480 | 0xc7: 0x8a02, // ul 481 | 0xc8: 0x5107, // pattern 482 | 0xc9: 0x4b60c, // onmousewheel 483 | 0xca: 0x35709, // ondragend 484 | 0xcb: 0xaf04, // ruby 485 | 0xcc: 0xc01, // p 486 | 0xcd: 0x31707, // onclose 487 | 0xce: 0x24205, // meter 488 | 0xcf: 0x11807, // bgsound 489 | 0xd2: 0x25106, // height 490 | 0xd4: 0x101, // b 491 | 0xd5: 0x2c308, // itemtype 492 | 0xd8: 0x1bb07, // caption 493 | 0xd9: 0x10c08, // disabled 494 | 0xdb: 0x33808, // menuitem 495 | 0xdc: 0x62003, // svg 496 | 0xdd: 0x18f05, // small 497 | 0xde: 0x44a04, // data 498 | 0xe0: 0x4cb08, // ononline 499 | 0xe1: 0x2a206, // mglyph 500 | 0xe3: 0x6505, // embed 501 | 0xe4: 0x10502, // tr 502 | 0xe5: 0x46b0b, // onloadstart 503 | 0xe7: 0x3c306, // srcdoc 504 | 0xeb: 0x5c408, // ontoggle 505 | 0xed: 0xe703, // bdo 506 | 0xee: 0x4702, // td 507 | 0xef: 0x8305, // aside 508 | 0xf0: 0x29402, // h2 509 | 0xf1: 0x52c08, // progress 510 | 0xf2: 0x12c0a, // blockquote 511 | 0xf4: 0xf005, // label 512 | 0xf5: 0x601, // i 513 | 0xf7: 0x9207, // rowspan 514 | 0xfb: 0x51709, // onplaying 515 | 0xfd: 0x2a103, // img 516 | 0xfe: 0xf608, // optgroup 517 | 0xff: 0x42307, // content 518 | 0x101: 0x53e0c, // onratechange 519 | 0x103: 0x3da0c, // onhashchange 520 | 0x104: 0x4807, // details 521 | 0x106: 0x40008, // download 522 | 0x109: 0x14009, // translate 523 | 0x10b: 0x4230f, // contenteditable 524 | 0x10d: 0x36b0b, // ondragleave 525 | 0x10e: 0x2106, // accept 526 | 0x10f: 0x57a08, // selected 527 | 0x112: 0x1f20a, // formaction 528 | 0x113: 0x5b506, // center 529 | 0x115: 0x45510, // onloadedmetadata 530 | 0x116: 0x12804, // link 531 | 0x117: 0xdd04, // time 532 | 0x118: 0x19f0b, // crossorigin 533 | 0x119: 0x3bd07, // onfocus 534 | 0x11a: 0x58704, // wrap 535 | 0x11b: 0x42204, // icon 536 | 0x11d: 0x28105, // video 537 | 0x11e: 0x4de05, // class 538 | 0x121: 0x5d40e, // onvolumechange 539 | 0x122: 0xaa06, // onblur 540 | 0x123: 0x2b909, // itemscope 541 | 0x124: 0x61105, // style 542 | 0x127: 0x41e06, // public 543 | 0x129: 0x2320e, // formnovalidate 544 | 0x12a: 0x58206, // onshow 545 | 0x12c: 0x51706, // onplay 546 | 0x12d: 0x3c804, // cite 547 | 0x12e: 0x2bc02, // ms 548 | 0x12f: 0xdb0c, // ontimeupdate 549 | 0x130: 0x10904, // kind 550 | 0x131: 0x2470a, // formtarget 551 | 0x135: 0x3af07, // onended 552 | 0x136: 0x26506, // hidden 553 | 0x137: 0x2c01, // s 554 | 0x139: 0x2280a, // formmethod 555 | 0x13a: 0x3e805, // input 556 | 0x13c: 0x50b02, // h6 557 | 0x13d: 0xc902, // ol 558 | 0x13e: 0x3420b, // oncuechange 559 | 0x13f: 0x1e50d, // foreignobject 560 | 0x143: 0x4e70e, // onbeforeunload 561 | 0x144: 0x2bd05, // scope 562 | 0x145: 0x39609, // onemptied 563 | 0x146: 0x14b05, // defer 564 | 0x147: 0xc103, // xmp 565 | 0x148: 0x39f10, // ondurationchange 566 | 0x149: 0x1903, // kbd 567 | 0x14c: 0x47609, // onmessage 568 | 0x14d: 0x60006, // option 569 | 0x14e: 0x2eb09, // minlength 570 | 0x14f: 0x32807, // checked 571 | 0x150: 0xce08, // autoplay 572 | 0x152: 0x202, // br 573 | 0x153: 0x2360a, // novalidate 574 | 0x156: 0x6307, // noembed 575 | 0x159: 0x31007, // onclick 576 | 0x15a: 0x47f0b, // onmousedown 577 | 0x15b: 0x3a708, // onchange 578 | 0x15e: 0x3f209, // oninvalid 579 | 0x15f: 0x2bd06, // scoped 580 | 0x160: 0x18808, // controls 581 | 0x161: 0x30b05, // muted 582 | 0x162: 0x58d08, // sortable 583 | 0x163: 0x51106, // usemap 584 | 0x164: 0x1b80a, // figcaption 585 | 0x165: 0x35706, // ondrag 586 | 0x166: 0x26b04, // high 587 | 0x168: 0x3c303, // src 588 | 0x169: 0x15706, // poster 589 | 0x16b: 0x1670e, // annotation-xml 590 | 0x16c: 0x5f704, // step 591 | 0x16d: 0x4, // abbr 592 | 0x16e: 0x1b06, // dialog 593 | 0x170: 0x1202, // li 594 | 0x172: 0x3ed02, // mo 595 | 0x175: 0x1d803, // for 596 | 0x176: 0x1a803, // ins 597 | 0x178: 0x55504, // size 598 | 0x179: 0x43210, // onlanguagechange 599 | 0x17a: 0x8607, // default 600 | 0x17b: 0x1a03, // bdi 601 | 0x17c: 0x4d30a, // onpagehide 602 | 0x17d: 0x6907, // dirname 603 | 0x17e: 0x21404, // type 604 | 0x17f: 0x1f204, // form 605 | 0x181: 0x28509, // oncanplay 606 | 0x182: 0x6103, // dfn 607 | 0x183: 0x46308, // tabindex 608 | 0x186: 0x6502, // em 609 | 0x187: 0x27404, // lang 610 | 0x189: 0x39108, // dropzone 611 | 0x18a: 0x4080a, // onkeypress 612 | 0x18b: 0x23c08, // datetime 613 | 0x18c: 0x16204, // cols 614 | 0x18d: 0x1, // a 615 | 0x18e: 0x4420c, // onloadeddata 616 | 0x190: 0xa605, // audio 617 | 0x192: 0x2e05, // tbody 618 | 0x193: 0x22c06, // method 619 | 0x195: 0xf404, // loop 620 | 0x196: 0x29606, // iframe 621 | 0x198: 0x2d504, // head 622 | 0x19e: 0x5f108, // manifest 623 | 0x19f: 0xb309, // autofocus 624 | 0x1a0: 0x14904, // code 625 | 0x1a1: 0x55906, // strong 626 | 0x1a2: 0x30308, // multiple 627 | 0x1a3: 0xc05, // param 628 | 0x1a6: 0x21107, // enctype 629 | 0x1a7: 0x5b304, // face 630 | 0x1a8: 0xfd09, // plaintext 631 | 0x1a9: 0x26e02, // h1 632 | 0x1aa: 0x59509, // onstalled 633 | 0x1ad: 0x3d406, // script 634 | 0x1ae: 0x2db06, // spacer 635 | 0x1af: 0x55108, // onresize 636 | 0x1b0: 0x4a20b, // onmouseover 637 | 0x1b1: 0x5cc08, // onunload 638 | 0x1b2: 0x56708, // onseeked 639 | 0x1b4: 0x2140d, // typemustmatch 640 | 0x1b5: 0x1cc06, // figure 641 | 0x1b6: 0x4950a, // onmouseout 642 | 0x1b7: 0x25e03, // pre 643 | 0x1b8: 0x50705, // width 644 | 0x1b9: 0x19906, // sorted 645 | 0x1bb: 0x5704, // nobr 646 | 0x1be: 0x5302, // tt 647 | 0x1bf: 0x1105, // align 648 | 0x1c0: 0x3e607, // oninput 649 | 0x1c3: 0x41807, // onkeyup 650 | 0x1c6: 0x1c00c, // onafterprint 651 | 0x1c7: 0x210e, // accept-charset 652 | 0x1c8: 0x33c06, // itemid 653 | 0x1c9: 0x3e809, // inputmode 654 | 0x1cb: 0x53306, // strike 655 | 0x1cc: 0x5a903, // sub 656 | 0x1cd: 0x10505, // track 657 | 0x1ce: 0x38605, // start 658 | 0x1d0: 0xd608, // basefont 659 | 0x1d6: 0x1aa06, // source 660 | 0x1d7: 0x18206, // legend 661 | 0x1d8: 0x2d405, // thead 662 | 0x1da: 0x8c05, // tfoot 663 | 0x1dd: 0x1ec06, // object 664 | 0x1de: 0x6e05, // media 665 | 0x1df: 0x1670a, // annotation 666 | 0x1e0: 0x20d0b, // formenctype 667 | 0x1e2: 0x3d208, // noscript 668 | 0x1e4: 0x55505, // sizes 669 | 0x1e5: 0x1fc0c, // autocomplete 670 | 0x1e6: 0x9504, // span 671 | 0x1e7: 0x9808, // noframes 672 | 0x1e8: 0x24b06, // target 673 | 0x1e9: 0x38f06, // ondrop 674 | 0x1ea: 0x2b306, // applet 675 | 0x1ec: 0x5a08, // reversed 676 | 0x1f0: 0x2a907, // isindex 677 | 0x1f3: 0x27008, // hreflang 678 | 0x1f5: 0x2f302, // h5 679 | 0x1f6: 0x4f307, // address 680 | 0x1fa: 0x2e103, // max 681 | 0x1fb: 0xc30b, // placeholder 682 | 0x1fc: 0x2f608, // textarea 683 | 0x1fe: 0x4ad09, // onmouseup 684 | 0x1ff: 0x3800b, // ondragstart 685 | } 686 | 687 | const atomText = "abbradiogrouparamalignmarkbdialogaccept-charsetbodyaccesskey" + 688 | "genavaluealtdetailsampatternobreversedfnoembedirnamediagroup" + 689 | "ingasyncanvasidefaultfooterowspanoframesetitleaudionblurubya" + 690 | "utofocusandboxmplaceholderautoplaybasefontimeupdatebdoncance" + 691 | "labelooptgrouplaintextrackindisabledivarbgsoundlowbrbigblink" + 692 | "blockquotebuttonabortranslatecodefercolgroupostercolorcolspa" + 693 | "nnotation-xmlcommandraggablegendcontrolsmallcoordsortedcross" + 694 | "originsourcefieldsetfigcaptionafterprintfigurequiredforeignO" + 695 | "bjectforeignobjectformactionautocompleteerrorformenctypemust" + 696 | "matchallengeformmethodformnovalidatetimeterformtargetheightm" + 697 | "lhgroupreloadhiddenhigh1hreflanghttp-equivideoncanplaythroug" + 698 | "h2iframeimageimglyph3isindexismappletitemscopeditemtypemarqu" + 699 | "eematheaderspacermaxlength4minlength5mtextareadonlymultiplem" + 700 | "utedonclickoncloseamlesspellcheckedoncontextmenuitemidoncuec" + 701 | "hangeondblclickondragendondragenterondragleaveondragoverondr" + 702 | "agstarticleondropzonemptiedondurationchangeonendedonerroronf" + 703 | "ocusrcdocitempropenoscriptonhashchangeoninputmodeloninvalido" + 704 | "nkeydownloadonkeypressrclangonkeyupublicontenteditableonlang" + 705 | "uagechangeonloadeddatalistingonloadedmetadatabindexonloadsta" + 706 | "rtonmessageonmousedownonmousemoveonmouseoutputonmouseoveronm" + 707 | "ouseuponmousewheelonofflineononlineonpagehidesclassectionbef" + 708 | "oreunloaddresshapeonpageshowidth6onpausemaponplayingonpopsta" + 709 | "teonprogresstrikeytypeonratechangeonresetonresizestrongonscr" + 710 | "ollonseekedonseekingonselectedonshowraponsortableonstalledon" + 711 | "storageonsubmitemrefacenteronsuspendontoggleonunloadonvolume" + 712 | "changeonwaitingoptimumanifestepromptoptionbeforeprintstylesu" + 713 | "mmarysupsvgsystemplate" 714 | --------------------------------------------------------------------------------