new file: README
new file: src/Makefile new file: src/document.go new file: src/io.go new file: src/node.go new file: src/test.xml new file: src/xmlx_test.go
This commit is contained in:
commit
0a655c2756
|
@ -0,0 +1,38 @@
|
|||
|
||||
Author: Jim Teeuwen <jimteeuwen@gmail.com>
|
||||
|
||||
This package wraps the standard XML library and uses it to build a node tree of
|
||||
any document you load. This allows you to look up nodes forwards and backwards,
|
||||
as well as perform search queries (no xpath support yet).
|
||||
|
||||
Nodes now simply become collections and don't require you to read them in the
|
||||
order in which the xml.Parser finds them.
|
||||
|
||||
xmlx.Document implements both these interfaces:
|
||||
|
||||
type ILoader interface {
|
||||
LoadFile(string) os.Error;
|
||||
LoadString(string) os.Error;
|
||||
LoadStream(*io.Reader) os.Error;
|
||||
}
|
||||
|
||||
type ISaver interface {
|
||||
SaveFile(string) os.Error;
|
||||
SaveString(string) (string, os.Error);
|
||||
SaveStream(*io.Writer) os.Error;
|
||||
}
|
||||
|
||||
This allows you to load/save xml data to and from pretty much any source.
|
||||
|
||||
The Document currently implements 2 simple search functions which allow you to
|
||||
look for specific nodes.
|
||||
|
||||
Document.SelectNode(namespace, name string) *Node;
|
||||
Document.SelectNodes(namespace, name string) []*Node;
|
||||
|
||||
SelectNode() returns the first, single node it finds matching the given name
|
||||
and namespace. SelectNodes() returns a slice containing all the matching nodes.
|
||||
|
||||
Note that these search functions can be invoked on individual nodes as well.
|
||||
This allows you to search only a subset of the entire document.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
include $(GOROOT)/src/Make.$(GOARCH)
|
||||
|
||||
TARG=xmlx
|
||||
GOFILES=document.go node.go io.go\
|
||||
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
|
@ -0,0 +1,271 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Author: Jim Teeuwen <jimteeuwen@gmail.com>
|
||||
|
||||
This package wraps the standard XML library and uses it to build a node tree of
|
||||
any document you load. This allows you to look up nodes forwards and backwards,
|
||||
as well as perform search queries (no xpath support yet).
|
||||
|
||||
Nodes now simply become collections and don't require you to read them in the
|
||||
order in which the xml.Parser finds them.
|
||||
|
||||
xmlx.Document implements both these interfaces:
|
||||
|
||||
type ILoader interface {
|
||||
LoadFile(string) os.Error;
|
||||
LoadString(string) os.Error;
|
||||
LoadStream(*io.Reader) os.Error;
|
||||
}
|
||||
|
||||
type ISaver interface {
|
||||
SaveFile(string) os.Error;
|
||||
SaveString(string) (string, os.Error);
|
||||
SaveStream(*io.Writer) os.Error;
|
||||
}
|
||||
|
||||
This allows you to load/save xml data to and from pretty much any source.
|
||||
|
||||
The Document currently implements 2 simple search functions which allow you to
|
||||
look for specific nodes.
|
||||
|
||||
Document.SelectNode(namespace, name string) *Node;
|
||||
Document.SelectNodes(namespace, name string) []*Node;
|
||||
|
||||
SelectNode() returns the first, single node it finds matching the given name
|
||||
and namespace. SelectNodes() returns a slice containing all the matching nodes.
|
||||
|
||||
Note that these search functions can be invoked on individual nodes as well.
|
||||
This allows you to search only a subset of the entire document.
|
||||
|
||||
*/
|
||||
package xmlx
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "strings"
|
||||
import "xml"
|
||||
import "fmt"
|
||||
|
||||
type Document struct {
|
||||
Version string;
|
||||
Encoding string;
|
||||
StandAlone string;
|
||||
SaveDocType bool;
|
||||
Root *Node;
|
||||
}
|
||||
|
||||
func New() *Document {
|
||||
return &Document{
|
||||
Version: "1.0",
|
||||
Encoding: "utf-8",
|
||||
StandAlone: "yes",
|
||||
SaveDocType: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Document) String() string {
|
||||
s, _ := this.SaveString();
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
func (this *Document) SelectNode(namespace, name string) *Node {
|
||||
return this.Root.SelectNode(namespace, name);
|
||||
}
|
||||
|
||||
func (this *Document) SelectNodes(namespace, name string) []*Node {
|
||||
return this.Root.SelectNodes(namespace, name);
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
// *** Satisfy ILoader interface
|
||||
// *****************************************************************************
|
||||
func (this *Document) LoadString(s string) (err os.Error) {
|
||||
xp := xml.NewParser(strings.NewReader(s));
|
||||
this.Root = NewNode(NT_ROOT);
|
||||
ct := this.Root;
|
||||
|
||||
for {
|
||||
tok, err := xp.Token();
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t1, ok := tok.(xml.SyntaxError);
|
||||
if ok {
|
||||
err = os.NewError(t1.String());
|
||||
return;
|
||||
}
|
||||
|
||||
t2, ok := tok.(xml.CharData);
|
||||
if ok {
|
||||
if ct != nil {
|
||||
ct.Value = strings.TrimSpace(string(t2))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
t3, ok := tok.(xml.Comment);
|
||||
if ok {
|
||||
t := NewNode(NT_COMMENT);
|
||||
t.Value = strings.TrimSpace(string(t3));
|
||||
if ct != nil {
|
||||
ct.AddChild(t)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
t4, ok := tok.(xml.Directive);
|
||||
if ok {
|
||||
t := NewNode(NT_DIRECTIVE);
|
||||
t.Value = strings.TrimSpace(string(t4));
|
||||
if ct != nil {
|
||||
ct.AddChild(t)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
t5, ok := tok.(xml.StartElement);
|
||||
if ok {
|
||||
t := NewNode(NT_ELEMENT);
|
||||
t.Name = t5.Name;
|
||||
t.Attributes = make([]Attr, len(t5.Attr));
|
||||
for i, v := range t5.Attr {
|
||||
t.Attributes[i].Name = v.Name;
|
||||
t.Attributes[i].Value = v.Value;
|
||||
}
|
||||
if ct != nil {
|
||||
ct.AddChild(t)
|
||||
}
|
||||
ct = t;
|
||||
continue
|
||||
}
|
||||
|
||||
t6, ok := tok.(xml.ProcInst);
|
||||
if ok {
|
||||
if t6.Target == "xml" { // xml doctype
|
||||
doctype := strings.TrimSpace(string(t6.Inst));
|
||||
|
||||
/* // Not needed. There is only xml version 1.0
|
||||
pos := strings.Index(doctype, `version="`);
|
||||
if pos > -1 {
|
||||
this.Version = doctype[pos+len(`version="`) : len(doctype)];
|
||||
pos = strings.Index(this.Version, `"`);
|
||||
this.Version = this.Version[0:pos];
|
||||
}
|
||||
*/
|
||||
|
||||
/* // Not needed. Any string we handle in Go is UTF8
|
||||
// encoded. This means we will save UTF8 data as well.
|
||||
pos = strings.Index(doctype, `encoding="`);
|
||||
if pos > -1 {
|
||||
this.Encoding = doctype[pos+len(`encoding="`) : len(doctype)];
|
||||
pos = strings.Index(this.Encoding, `"`);
|
||||
this.Encoding = this.Encoding[0:pos];
|
||||
}
|
||||
*/
|
||||
|
||||
pos := strings.Index(doctype, `standalone="`);
|
||||
if pos > -1 {
|
||||
this.StandAlone = doctype[pos+len(`standalone="`) : len(doctype)];
|
||||
pos = strings.Index(this.StandAlone, `"`);
|
||||
this.StandAlone = this.StandAlone[0:pos];
|
||||
}
|
||||
} else {
|
||||
t := NewNode(NT_PROCINST);
|
||||
t.Target = strings.TrimSpace(t6.Target);
|
||||
t.Value = strings.TrimSpace(string(t6.Inst));
|
||||
if ct != nil {
|
||||
ct.AddChild(t)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
_, ok = tok.(xml.EndElement);
|
||||
if ok {
|
||||
ct = ct.Parent;
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Document) LoadFile(path string) (err os.Error) {
|
||||
file, err := os.Open(path, os.O_RDONLY, 0600);
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close();
|
||||
|
||||
content := "";
|
||||
buff := make([]byte, 256);
|
||||
for {
|
||||
_, err := file.Read(buff);
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
content += string(buff);
|
||||
}
|
||||
|
||||
err = this.LoadString(content);
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Document) LoadStream(r *io.Reader) (err os.Error) {
|
||||
content := "";
|
||||
buff := make([]byte, 256);
|
||||
for {
|
||||
_, err := r.Read(buff);
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
content += string(buff);
|
||||
}
|
||||
|
||||
err = this.LoadString(content);
|
||||
return;
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
// *** Satisfy ISaver interface
|
||||
// *****************************************************************************
|
||||
func (this *Document) SaveFile(path string) (err os.Error) {
|
||||
file, err := os.Open(path, os.O_WRONLY | os.O_CREAT, 0600);
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close();
|
||||
|
||||
content, err := this.SaveString();
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
file.Write(strings.Bytes(content));
|
||||
return
|
||||
}
|
||||
|
||||
func (this *Document) SaveString() (s string, err os.Error) {
|
||||
if this.SaveDocType {
|
||||
s = fmt.Sprintf(`<?xml version="%s" encoding="%s" standalone="%s"?>`,
|
||||
this.Version, this.Encoding, this.StandAlone)
|
||||
}
|
||||
|
||||
s += this.Root.String();
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Document) SaveStream(w *io.Writer) (err os.Error) {
|
||||
s, err := this.SaveString();
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w.Write(strings.Bytes(s));
|
||||
return;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package xmlx
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
|
||||
type ILoader interface {
|
||||
LoadFile(string) os.Error;
|
||||
LoadString(string) os.Error;
|
||||
LoadStream(*io.Reader) os.Error;
|
||||
}
|
||||
|
||||
type ISaver interface {
|
||||
SaveFile(string) os.Error;
|
||||
SaveString(string) (string, os.Error);
|
||||
SaveStream(*io.Writer) os.Error;
|
||||
}
|
||||
|
||||
type ILoaderSaver interface {
|
||||
ILoader;
|
||||
ISaver;
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
package xmlx
|
||||
|
||||
import "xml"
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
NT_ROOT = 0x00;
|
||||
NT_DIRECTIVE = 0x01;
|
||||
NT_PROCINST = 0x02;
|
||||
NT_COMMENT = 0x03;
|
||||
NT_ELEMENT = 0x04;
|
||||
)
|
||||
|
||||
type Attr struct {
|
||||
Name xml.Name;
|
||||
Value string;
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Type byte;
|
||||
Name xml.Name;
|
||||
Children []*Node;
|
||||
Attributes []Attr;
|
||||
Parent *Node;
|
||||
Value string;
|
||||
|
||||
// procinst field
|
||||
Target string;
|
||||
}
|
||||
|
||||
func NewNode(tid byte) *Node { return &Node{Type: tid} }
|
||||
|
||||
func (this *Node) SelectNode(namespace, name string) *Node {
|
||||
return rec_SelectNode(this, namespace, name);
|
||||
}
|
||||
|
||||
func rec_SelectNode(cn *Node, namespace, name string) *Node {
|
||||
if cn.Name.Space == namespace && cn.Name.Local == name {
|
||||
return cn;
|
||||
}
|
||||
|
||||
for _, v := range cn.Children {
|
||||
tn := rec_SelectNode(v, namespace, name);
|
||||
if tn != nil { return tn }
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
func (this *Node) SelectNodes(namespace, name string) []*Node {
|
||||
list := make([]*Node, 0);
|
||||
rec_SelectNodes(this, namespace, name, &list);
|
||||
return list;
|
||||
}
|
||||
|
||||
func rec_SelectNodes(cn *Node, namespace, name string, list *[]*Node) {
|
||||
if cn.Name.Space == namespace && cn.Name.Local == name {
|
||||
slice := make([]*Node, len(*list) + 1);
|
||||
for i,v := range *list {
|
||||
slice[i] = v;
|
||||
}
|
||||
slice[len(slice) - 1] = cn;
|
||||
*list = slice;
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range cn.Children {
|
||||
rec_SelectNodes(v, namespace, name, list);
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Node) String() (s string) {
|
||||
switch this.Type {
|
||||
case NT_PROCINST:
|
||||
s = this.printProcInst()
|
||||
case NT_COMMENT:
|
||||
s = this.printComment()
|
||||
case NT_DIRECTIVE:
|
||||
s = this.printDirective()
|
||||
case NT_ELEMENT:
|
||||
s = this.printElement()
|
||||
case NT_ROOT:
|
||||
s = this.printRoot()
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Node) printRoot() (s string) {
|
||||
for _, v := range this.Children {
|
||||
s += v.String()
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Node) printProcInst() (s string) {
|
||||
s = "<?" + this.Target + " " + this.Value + "?>";
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Node) printComment() (s string) {
|
||||
s = "<!-- " + this.Value + " -->";
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Node) printDirective() (s string) {
|
||||
s = "<!" + this.Value + "!>";
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Node) printElement() (s string) {
|
||||
if len(this.Name.Space) > 0 {
|
||||
s = "<" + this.Name.Space + ":" + this.Name.Local
|
||||
} else {
|
||||
s = "<" + this.Name.Local
|
||||
}
|
||||
|
||||
for _, v := range this.Attributes {
|
||||
if len(v.Name.Space) > 0 {
|
||||
s += fmt.Sprintf(` %s:%s="%s"`, v.Name.Space, v.Name.Local, v.Value)
|
||||
} else {
|
||||
s += fmt.Sprintf(` %s="%s"`, v.Name.Local, v.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if len(this.Children) == 0 && len(this.Value) == 0 {
|
||||
s += " />";
|
||||
return;
|
||||
}
|
||||
|
||||
s += ">";
|
||||
|
||||
for _, v := range this.Children {
|
||||
s += v.String()
|
||||
}
|
||||
|
||||
s += this.Value;
|
||||
if len(this.Name.Space) > 0 {
|
||||
s += "</" + this.Name.Space + ":" + this.Name.Local + ">"
|
||||
} else {
|
||||
s += "</" + this.Name.Local + ">"
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
func (this *Node) AddChild(t *Node) {
|
||||
if t.Parent != nil {
|
||||
t.Parent.RemoveChild(t)
|
||||
}
|
||||
t.Parent = this;
|
||||
|
||||
slice := make([]*Node, len(this.Children)+1);
|
||||
for i, v := range this.Children {
|
||||
slice[i] = v
|
||||
}
|
||||
slice[len(slice)-1] = t;
|
||||
this.Children = slice;
|
||||
}
|
||||
|
||||
func (this *Node) RemoveChild(t *Node) {
|
||||
pos := -1;
|
||||
for i, v := range this.Children {
|
||||
if v == t {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if pos == -1 {
|
||||
return
|
||||
}
|
||||
slice := make([]*Node, len(this.Children)-1);
|
||||
|
||||
idx := 0;
|
||||
for i, v := range this.Children {
|
||||
if i != pos {
|
||||
slice[idx] = v;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
t.Parent = nil;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<rss version="0.91">
|
||||
<channel>
|
||||
<title>WriteTheWeb</title>
|
||||
<link>http://writetheweb.com</link>
|
||||
<description>News for web users that write back</description>
|
||||
<language>en-us</language>
|
||||
<copyright>Copyright 2000, WriteTheWeb team.</copyright>
|
||||
<managingEditor>editor@writetheweb.com</managingEditor>
|
||||
<webMaster>webmaster@writetheweb.com</webMaster>
|
||||
<image>
|
||||
<title>WriteTheWeb</title>
|
||||
<url>http://writetheweb.com/images/mynetscape88.gif</url>
|
||||
<link>http://writetheweb.com</link>
|
||||
<width>88</width>
|
||||
<height>31</height>
|
||||
<description>News for web users that write back</description>
|
||||
</image>
|
||||
<item>
|
||||
<title>Giving the world a pluggable Gnutella</title>
|
||||
<link>http://writetheweb.com/read.php?item=24</link>
|
||||
<description>WorldOS is a framework on which to build programs that work like Freenet or Gnutella -allowing distributed applications using peer-to-peer routing.</description>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>Syndication discussions hot up</title>
|
||||
<link>http://writetheweb.com/read.php?item=23</link>
|
||||
<description>After a period of dormancy, the Syndication mailing list has become active again, with contributions from leaders in traditional media and Web syndication.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Personal web server integrates file sharing and messaging</title>
|
||||
<link>http://writetheweb.com/read.php?item=22</link>
|
||||
<description>The Magi Project is an innovative project to create a combined personal web server and messaging system that enables the sharing and synchronization of information across desktop, laptop and palmtop devices.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Syndication and Metadata</title>
|
||||
<link>http://writetheweb.com/read.php?item=21</link>
|
||||
<description>RSS is probably the best known metadata format around. RDF is probably one of the least understood. In this essay, published on my O'Reilly Network weblog, I argue that the next generation of RSS should be based on RDF.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>UK bloggers get organised</title>
|
||||
<link>http://writetheweb.com/read.php?item=20</link>
|
||||
<description>Looks like the weblogs scene is gathering pace beyond the shores of the US. There's now a UK-specific page on weblogs.com, and a mailing list at egroups.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Yournamehere.com more important than anything</title>
|
||||
<link>http://writetheweb.com/read.php?item=19</link>
|
||||
<description>Whatever you're publishing on the web, your site name is the most valuable asset you have, according to Carl Steadman.</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
|
@ -0,0 +1,57 @@
|
|||
package xmlx
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDoc(t *testing.T) {
|
||||
doc := New();
|
||||
err := doc.LoadFile("test.xml");
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if len(doc.Root.Children) == 0 {
|
||||
t.Errorf("Root node has no children.", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
doc := New();
|
||||
err := doc.LoadFile("test.xml");
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("LoadFile(): %s", err);
|
||||
return;
|
||||
}
|
||||
|
||||
err = doc.SaveFile("test1.xml");
|
||||
if err != nil {
|
||||
t.Errorf("SaveFile(): %s", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeSearch(t *testing.T) {
|
||||
doc := New();
|
||||
err := doc.LoadFile("test.xml");
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("LoadFile(): %s", err);
|
||||
return;
|
||||
}
|
||||
|
||||
node := doc.SelectNode("", "item");
|
||||
if node == nil {
|
||||
t.Errorf("SelectNode(): No node found.");
|
||||
return;
|
||||
}
|
||||
|
||||
nodes := doc.SelectNodes("", "item");
|
||||
if len(nodes) == 0 {
|
||||
t.Errorf("SelectNodes(): no nodes found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue