github.com/boki/go-xmp@v1.0.1/xmp/document.go (about) 1 // Copyright (c) 2017-2018 Alexander Eichhorn 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 // not use this file except in compliance with the License. You may obtain 5 // a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations 13 // under the License. 14 15 package xmp 16 17 import ( 18 "fmt" 19 "reflect" 20 ) 21 22 type Document struct { 23 // name and version of the toolkit that generated an XMP document instance 24 toolkit string 25 26 nodes NodeList 27 28 // flag indicating the document content has changed 29 dirty bool 30 31 // rdf:about 32 about string 33 34 // local namespace map for tracking registered namespaces 35 intNsMap map[string]*Namespace 36 37 // local namespace map for tracking unknown namespaces 38 extNsMap map[string]*Namespace 39 } 40 41 // high-level XMP document interface 42 func NewDocument() *Document { 43 d := &Document{ 44 toolkit: XMP_TOOLKIT_VERSION, 45 nodes: make(NodeList, 0), 46 intNsMap: make(map[string]*Namespace), 47 extNsMap: make(map[string]*Namespace), 48 } 49 return d 50 } 51 52 func (d *Document) SetDirty() { 53 d.dirty = true 54 } 55 56 func (d Document) IsDirty() bool { 57 return d.dirty 58 } 59 60 func (d *Document) Close() { 61 if d == nil { 62 return 63 } 64 for _, v := range d.nodes { 65 v.Close() 66 } 67 d.nodes = nil 68 } 69 70 // cross-model sync, must be explicitly called to merge across models 71 func (d *Document) SyncModels() error { 72 for _, n := range d.nodes { 73 if n.Model != nil { 74 if err := n.Model.SyncModel(d); err != nil { 75 return err 76 } 77 } 78 } 79 return nil 80 } 81 82 // implicit sync to align standard properties across models (e.g. xmp <-> photoshop) 83 // called each time a model is loaded from XMP/XML or XMP/JSON 84 func (d *Document) syncFromXMP() error { 85 for _, n := range d.nodes { 86 if n.Model != nil { 87 if err := n.Model.SyncFromXMP(d); err != nil { 88 return err 89 } 90 } 91 } 92 return nil 93 } 94 95 // called each time a model is stored as XMP/XML or XMP/JSON 96 func (d *Document) syncToXMP() error { 97 if !d.dirty { 98 return nil 99 } 100 for _, n := range d.nodes { 101 if n.Model != nil { 102 if err := n.Model.SyncToXMP(d); err != nil { 103 return err 104 } 105 } 106 } 107 d.dirty = false 108 return nil 109 } 110 111 func (d *Document) Namespaces() NamespaceList { 112 var l NamespaceList 113 for _, v := range d.intNsMap { 114 l = append(l, v) 115 } 116 for _, v := range d.extNsMap { 117 l = append(l, v) 118 } 119 return l 120 } 121 122 func (d *Document) Nodes() NodeList { 123 return d.nodes 124 } 125 126 func (d *Document) FindNode(ns *Namespace) *Node { 127 return d.nodes.FindNode(ns) 128 } 129 130 func (d *Document) FindModel(ns *Namespace) Model { 131 prefix := ns.GetName() 132 for _, v := range d.nodes { 133 if v.Model != nil && v.Model.Can(prefix) { 134 return v.Model 135 } 136 } 137 return nil 138 } 139 140 func (d Document) FindNs(name, uri string) *Namespace { 141 var ns *Namespace 142 if uri != "" { 143 ns = d.findNsByURI(uri) 144 } 145 if ns == nil { 146 ns = d.findNsByPrefix(getPrefix(name)) 147 } 148 return ns 149 } 150 151 func (d *Document) MakeModel(ns *Namespace) (Model, error) { 152 m := d.FindModel(ns) 153 if m == nil { 154 if m = ns.NewModel(); m == nil { 155 return nil, fmt.Errorf("xmp: cannot create '%s' model", ns.GetName()) 156 } 157 n := NewNode(ns.XMLName("")) 158 n.Model = m 159 d.nodes = append(d.nodes, n) 160 for _, v := range m.Namespaces() { 161 d.intNsMap[v.GetURI()] = v 162 } 163 d.SetDirty() 164 } 165 return m, nil 166 } 167 168 func (d *Document) AddModel(v Model) (*Node, error) { 169 if v == nil { 170 return nil, fmt.Errorf("xmp: AddModel called with nil model") 171 } 172 ns := v.Namespaces() 173 if len(ns) == 0 { 174 return nil, fmt.Errorf("xmp: model '%s' must implement at least one namespace", reflect.TypeOf(v).String()) 175 } 176 for _, v := range ns { 177 d.intNsMap[v.GetURI()] = v 178 } 179 n := d.FindNode(ns[0]) 180 if n == nil { 181 n = NewNode(ns[0].XMLName("")) 182 d.nodes = append(d.nodes, n) 183 } 184 n.Model = v 185 d.SetDirty() 186 return n, nil 187 } 188 189 func (d *Document) RemoveNamespace(ns *Namespace) bool { 190 if ns == nil { 191 return false 192 } 193 name := ns.GetName() 194 removed := false 195 for i, v := range d.nodes { 196 if v.Name() == name { 197 d.nodes = append(d.nodes[:i], d.nodes[i+1:]...) 198 removed = true 199 delete(d.intNsMap, ns.GetURI()) 200 delete(d.extNsMap, ns.GetURI()) 201 v.Close() 202 d.SetDirty() 203 break 204 } 205 } 206 return removed 207 } 208 209 func (d *Document) RemoveNamespaceByName(n string) bool { 210 ns := d.findNsByPrefix(n) 211 return d.RemoveNamespace(ns) 212 } 213 214 // will delete all document nodes matching the list 215 func (d *Document) RemoveNamespaces(remove NamespaceList) bool { 216 removed := false 217 for _, ns := range remove { 218 r := d.RemoveNamespace(ns) 219 removed = removed || r 220 } 221 return removed 222 } 223 224 // will keep document nodes matching the list and delete all others. 225 // returns true if any namespace was removed. 226 func (d *Document) FilterNamespaces(keep NamespaceList) bool { 227 removed := false 228 allNs := make(NamespaceList, 0) 229 // stage one: collect all top-level namespaces 230 for _, v := range d.nodes { 231 ns := d.findNsByPrefix(v.Namespace()) 232 if ns != nil { 233 allNs = append(allNs, ns) 234 } 235 } 236 // stage two: remove 237 for _, v := range allNs { 238 if !keep.Contains(v) { 239 r := d.RemoveNamespace(v) 240 removed = removed || r 241 } 242 } 243 return removed 244 } 245 246 type DocumentArray []*Document 247 248 func (x DocumentArray) Typ() ArrayType { 249 return ArrayTypeUnordered 250 } 251 252 func (x DocumentArray) MarshalXMP(e *Encoder, node *Node, m Model) error { 253 return MarshalArray(e, node, x.Typ(), x) 254 } 255 256 func (x *DocumentArray) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 257 return UnmarshalArray(d, node, x.Typ(), x) 258 } 259 260 func (d Document) findNsByURI(uri string) *Namespace { 261 if v, ok := d.intNsMap[uri]; ok { 262 return v 263 } 264 if v, ok := d.extNsMap[uri]; ok { 265 return v 266 } 267 return nil 268 } 269 270 func (d Document) findNsByPrefix(pre string) *Namespace { 271 for _, v := range d.intNsMap { 272 if v.GetName() == pre { 273 return v 274 } 275 } 276 for _, v := range d.extNsMap { 277 if v.GetName() == pre { 278 return v 279 } 280 } 281 if ns, err := NsRegistry.GetNamespace(pre); err == nil { 282 return ns 283 } 284 return nil 285 }