ifd.go: Moved implementations of IfdMapping and LoadStandardIfds to exifcommon.ifd

In lieu of dropping in next release. We needed this in order to write
some unit-tests for exifcommon functionality, and this is common
functionality anyway (by any definition of common thus far).

- common/ifd.go: Added NewIfdIdentityFromString. The above allowed us to
  cover this with unit-tests.
  - This was required for go-exif-knife, to get IFD-paths from the
    command-line.
dustin/master
Dustin Oprea 2020-05-27 12:50:02 -04:00
parent 2a1e3f0fa1
commit 1a62daf305
6 changed files with 538 additions and 399 deletions

View File

@ -1,12 +1,407 @@
package exifcommon
import (
"errors"
"fmt"
"strings"
"github.com/dsoprea/go-logging"
)
var (
ifdLogger = log.NewLogger("exifcommon.ifd")
)
var (
ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent")
)
// MappedIfd is one node in the IFD-mapping.
type MappedIfd struct {
ParentTagId uint16
Placement []uint16
Path []string
Name string
TagId uint16
Children map[uint16]*MappedIfd
}
// String returns a descriptive string.
func (mi *MappedIfd) String() string {
pathPhrase := mi.PathPhrase()
return fmt.Sprintf("MappedIfd<(0x%04X) [%s] PATH=[%s]>", mi.TagId, mi.Name, pathPhrase)
}
// PathPhrase returns a non-fully-qualified IFD path.
func (mi *MappedIfd) PathPhrase() string {
return strings.Join(mi.Path, "/")
}
// TODO(dustin): Refactor this to use IfdIdentity structs.
// IfdMapping describes all of the IFDs that we currently recognize.
type IfdMapping struct {
rootNode *MappedIfd
}
// NewIfdMapping returns a new IfdMapping struct.
func NewIfdMapping() (ifdMapping *IfdMapping) {
rootNode := &MappedIfd{
Path: make([]string, 0),
Children: make(map[uint16]*MappedIfd),
}
return &IfdMapping{
rootNode: rootNode,
}
}
// NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the
// standard IFDs.
func NewIfdMappingWithStandard() (ifdMapping *IfdMapping) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.Panic(err)
}
}()
// RELEASE(dustin): Add error return on next release
im := NewIfdMapping()
err := LoadStandardIfds(im)
log.PanicIf(err)
return im
}
// Get returns the node given the path slice.
func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ptr := im.rootNode
for _, tagId := range parentPlacement {
if descendantPtr, found := ptr.Children[tagId]; found == false {
log.Panicf("ifd child with tag-ID (%04x) not registered: [%s]", tagId, ptr.PathPhrase())
} else {
ptr = descendantPtr
}
}
return ptr, nil
}
// GetWithPath returns the node given the path string.
func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if pathPhrase == "" {
log.Panicf("path-phrase is empty")
}
path := strings.Split(pathPhrase, "/")
ptr := im.rootNode
for _, name := range path {
var hit *MappedIfd
for _, mi := range ptr.Children {
if mi.Name == name {
hit = mi
break
}
}
if hit == nil {
log.Panicf("ifd child with name [%s] not registered: [%s]", name, ptr.PathPhrase())
}
ptr = hit
}
return ptr, nil
}
// GetChild is a convenience function to get the child path for a given parent
// placement and child tag-ID.
func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
mi, err = im.GetWithPath(parentPathPhrase)
log.PanicIf(err)
for _, childMi := range mi.Children {
if childMi.TagId == tagId {
return childMi, nil
}
}
// Whether or not an IFD is defined in data, such an IFD is not registered
// and would be unknown.
log.Panic(ErrChildIfdNotMapped)
return nil, nil
}
// IfdTagIdAndIndex represents a specific part of the IFD path.
//
// This is a legacy type.
type IfdTagIdAndIndex struct {
Name string
TagId uint16
Index int
}
// String returns a descriptive string.
func (itii IfdTagIdAndIndex) String() string {
return fmt.Sprintf("IfdTagIdAndIndex<NAME=[%s] ID=(%04x) INDEX=(%d)>", itii.Name, itii.TagId, itii.Index)
}
// ResolvePath takes a list of names, which can also be suffixed with indices
// (to identify the second, third, etc.. sibling IFD) and returns a list of
// tag-IDs and those indices.
//
// Example:
//
// - IFD/Exif/Iop
// - IFD0/Exif/Iop
//
// This is the only call that supports adding the numeric indices.
func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
pathPhrase = strings.TrimSpace(pathPhrase)
if pathPhrase == "" {
log.Panicf("can not resolve empty path-phrase")
}
path := strings.Split(pathPhrase, "/")
lineage = make([]IfdTagIdAndIndex, len(path))
ptr := im.rootNode
empty := IfdTagIdAndIndex{}
for i, name := range path {
indexByte := name[len(name)-1]
index := 0
if indexByte >= '0' && indexByte <= '9' {
index = int(indexByte - '0')
name = name[:len(name)-1]
}
itii := IfdTagIdAndIndex{}
for _, mi := range ptr.Children {
if mi.Name != name {
continue
}
itii.Name = name
itii.TagId = mi.TagId
itii.Index = index
ptr = mi
break
}
if itii == empty {
log.Panicf("ifd child with name [%s] not registered: [%s]", name, pathPhrase)
}
lineage[i] = itii
}
return lineage, nil
}
// FqPathPhraseFromLineage returns the fully-qualified IFD path from the slice.
func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string) {
fqPathParts := make([]string, len(lineage))
for i, itii := range lineage {
if itii.Index > 0 {
fqPathParts[i] = fmt.Sprintf("%s%d", itii.Name, itii.Index)
} else {
fqPathParts[i] = itii.Name
}
}
return strings.Join(fqPathParts, "/")
}
// PathPhraseFromLineage returns the non-fully-qualified IFD path from the
// slice.
func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string) {
pathParts := make([]string, len(lineage))
for i, itii := range lineage {
pathParts[i] = itii.Name
}
return strings.Join(pathParts, "/")
}
// StripPathPhraseIndices returns a non-fully-qualified path-phrase (no
// indices).
func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
lineage, err := im.ResolvePath(pathPhrase)
log.PanicIf(err)
strippedPathPhrase = im.PathPhraseFromLineage(lineage)
return strippedPathPhrase, nil
}
// Add puts the given IFD at the given position of the tree. The position of the
// tree is referred to as the placement and is represented by a set of tag-IDs,
// where the leftmost is the root tag and the tags going to the right are
// progressive descendants.
func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! It would be nicer to provide a list of names in the placement rather than tag-IDs.
ptr, err := im.Get(parentPlacement)
log.PanicIf(err)
path := make([]string, len(parentPlacement)+1)
if len(parentPlacement) > 0 {
copy(path, ptr.Path)
}
path[len(path)-1] = name
placement := make([]uint16, len(parentPlacement)+1)
if len(placement) > 0 {
copy(placement, ptr.Placement)
}
placement[len(placement)-1] = tagId
childIfd := &MappedIfd{
ParentTagId: ptr.TagId,
Path: path,
Placement: placement,
Name: name,
TagId: tagId,
Children: make(map[uint16]*MappedIfd),
}
if _, found := ptr.Children[tagId]; found == true {
log.Panicf("child IFD with tag-ID (%04x) already registered under IFD [%s] with tag-ID (%04x)", tagId, ptr.Name, ptr.TagId)
}
ptr.Children[tagId] = childIfd
return nil
}
func (im *IfdMapping) dumpLineages(stack []*MappedIfd, input []string) (output []string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
currentIfd := stack[len(stack)-1]
output = input
for _, childIfd := range currentIfd.Children {
stackCopy := make([]*MappedIfd, len(stack)+1)
copy(stackCopy, stack)
stackCopy[len(stack)] = childIfd
// Add to output, but don't include the obligatory root node.
parts := make([]string, len(stackCopy)-1)
for i, mi := range stackCopy[1:] {
parts[i] = mi.Name
}
output = append(output, strings.Join(parts, "/"))
output, err = im.dumpLineages(stackCopy, output)
log.PanicIf(err)
}
return output, nil
}
// DumpLineages returns a slice of strings representing all mappings.
func (im *IfdMapping) DumpLineages() (output []string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
stack := []*MappedIfd{im.rootNode}
output = make([]string, 0)
output, err = im.dumpLineages(stack, output)
log.PanicIf(err)
return output, nil
}
// LoadStandardIfds loads the standard IFDs into the mapping.
func LoadStandardIfds(im *IfdMapping) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
err = im.Add(
[]uint16{},
IfdStandardIfdIdentity.TagId(), IfdStandardIfdIdentity.Name())
log.PanicIf(err)
err = im.Add(
[]uint16{IfdStandardIfdIdentity.TagId()},
IfdExifStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.Name())
log.PanicIf(err)
err = im.Add(
[]uint16{IfdStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.TagId()},
IfdExifIopStandardIfdIdentity.TagId(), IfdExifIopStandardIfdIdentity.Name())
log.PanicIf(err)
err = im.Add(
[]uint16{IfdStandardIfdIdentity.TagId()},
IfdGpsInfoStandardIfdIdentity.TagId(), IfdGpsInfoStandardIfdIdentity.Name())
log.PanicIf(err)
return nil
}
// IfdTag describes a single IFD tag and its parent (if any).
type IfdTag struct {
parentIfdTag *IfdTag
@ -105,6 +500,51 @@ func NewIfdIdentity(ifdTag IfdTag, parts ...IfdIdentityPart) (ii *IfdIdentity) {
return ii
}
// NewIfdIdentityFromString parses a string like "IFD/Exif" or "IFD1" or
// something more exotic with custom IFDs ("SomeIFD4/SomeChildIFD6"). Note that
// this will valid the unindexed IFD structure (because the standard tags from
// the specification are unindexed), but not, obviously, any indices (e.g.
// the numbers in "IFD0", "IFD1", "SomeIFD4/SomeChildIFD6"). It is
// required for the caller to check whether these specific instances
// were actually parsed out of the stream.
func NewIfdIdentityFromString(im *IfdMapping, fqIfdPath string) (ii *IfdIdentity, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
lineage, err := im.ResolvePath(fqIfdPath)
log.PanicIf(err)
var lastIt *IfdTag
identityParts := make([]IfdIdentityPart, len(lineage))
for i, itii := range lineage {
// Build out the tag that will eventually point to the IFD represented
// by the right-most part in the IFD path.
it := &IfdTag{
parentIfdTag: lastIt,
tagId: itii.TagId,
name: itii.Name,
}
lastIt = it
// Create the next IfdIdentity part.
iip := IfdIdentityPart{
Name: itii.Name,
Index: itii.Index,
}
identityParts[i] = iip
}
ii = NewIfdIdentity(*lastIt, identityParts...)
return ii, nil
}
func (ii *IfdIdentity) getFqIfdPath() string {
partPhrases := make([]string, len(ii.parts))
for i, iip := range ii.parts {

View File

@ -1,4 +1,4 @@
package exif
package exifcommon
import (
"fmt"
@ -7,8 +7,6 @@ import (
"testing"
"github.com/dsoprea/go-logging"
"github.com/dsoprea/go-exif/v2/common"
)
func TestIfdMapping_Add(t *testing.T) {
@ -109,16 +107,16 @@ func TestIfdMapping_Get(t *testing.T) {
log.PanicIf(err)
mi, err := im.Get([]uint16{
exifcommon.IfdStandardIfdIdentity.TagId(),
exifcommon.IfdExifStandardIfdIdentity.TagId(),
exifcommon.IfdExifIopStandardIfdIdentity.TagId(),
IfdStandardIfdIdentity.TagId(),
IfdExifStandardIfdIdentity.TagId(),
IfdExifIopStandardIfdIdentity.TagId(),
})
log.PanicIf(err)
if mi.ParentTagId != exifcommon.IfdExifStandardIfdIdentity.TagId() {
if mi.ParentTagId != IfdExifStandardIfdIdentity.TagId() {
t.Fatalf("Parent tag-ID not correct")
} else if mi.TagId != exifcommon.IfdExifIopStandardIfdIdentity.TagId() {
} else if mi.TagId != IfdExifIopStandardIfdIdentity.TagId() {
t.Fatalf("Tag-ID not correct")
} else if mi.Name != "Iop" {
t.Fatalf("name not correct")
@ -136,9 +134,9 @@ func TestIfdMapping_GetWithPath(t *testing.T) {
mi, err := im.GetWithPath("IFD/Exif/Iop")
log.PanicIf(err)
if mi.ParentTagId != exifcommon.IfdExifStandardIfdIdentity.TagId() {
if mi.ParentTagId != IfdExifStandardIfdIdentity.TagId() {
t.Fatalf("Parent tag-ID not correct")
} else if mi.TagId != exifcommon.IfdExifIopStandardIfdIdentity.TagId() {
} else if mi.TagId != IfdExifIopStandardIfdIdentity.TagId() {
t.Fatalf("Tag-ID not correct")
} else if mi.Name != "Iop" {
t.Fatalf("name not correct")
@ -269,3 +267,67 @@ func TestIfdMapping_NewIfdMappingWithStandard(t *testing.T) {
t.Fatalf("Standard IFDs not loaded correctly.")
}
}
func TestNewIfdIdentityFromString_Valid_WithoutIndexes(t *testing.T) {
im := NewIfdMapping()
err := LoadStandardIfds(im)
log.PanicIf(err)
fqIfdPath := "IFD/Exif"
ii, err := NewIfdIdentityFromString(im, fqIfdPath)
log.PanicIf(err)
if ii.String() != fqIfdPath {
t.Fatalf("'%s' IFD-path was not parsed correctly: [%s]", fqIfdPath, ii.String())
}
}
func TestNewIfdIdentityFromString_Valid_WithIndexes(t *testing.T) {
im := NewIfdMapping()
err := LoadStandardIfds(im)
log.PanicIf(err)
fqIfdPath := "IFD2/Exif4"
ii, err := NewIfdIdentityFromString(im, fqIfdPath)
log.PanicIf(err)
if ii.String() != fqIfdPath {
t.Fatalf("'%s' IFD-path was not parsed correctly: [%s]", fqIfdPath, ii.String())
}
}
func TestNewIfdIdentityFromString_Invalid_IfdPathJustRoot(t *testing.T) {
im := NewIfdMapping()
err := LoadStandardIfds(im)
log.PanicIf(err)
fqIfdPath := "XYZ"
_, err = NewIfdIdentityFromString(im, fqIfdPath)
if err == nil {
t.Fatalf("Expected error from invalid path.")
} else if err.Error() != "ifd child with name [XYZ] not registered: [XYZ]" {
log.Panic(err)
}
}
func TestNewIfdIdentityFromString_Invalid_IfdPathWithSubdirectory(t *testing.T) {
im := NewIfdMapping()
err := LoadStandardIfds(im)
log.PanicIf(err)
fqIfdPath := "IFD/XYZ"
_, err = NewIfdIdentityFromString(im, fqIfdPath)
if err == nil {
t.Fatalf("Expected error from invalid path.")
} else if err.Error() != "ifd child with name [XYZ] not registered: [IFD/XYZ]" {
log.Panic(err)
}
}

View File

@ -192,7 +192,7 @@ func ParseExifHeader(data []byte) (eh ExifHeader, err error) {
}
// Visit recursively invokes a callback for every tag.
func Visit(rootIfdIdentity *exifcommon.IfdIdentity, ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte, visitor TagVisitorFn) (eh ExifHeader, furthestOffset uint32, err error) {
func Visit(rootIfdIdentity *exifcommon.IfdIdentity, ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, exifData []byte, visitor TagVisitorFn) (eh ExifHeader, furthestOffset uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -213,7 +213,7 @@ func Visit(rootIfdIdentity *exifcommon.IfdIdentity, ifdMapping *IfdMapping, tagI
}
// Collect recursively builds a static structure of all IFDs and tags.
func Collect(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte) (eh ExifHeader, index IfdIndex, err error) {
func Collect(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, exifData []byte) (eh ExifHeader, index IfdIndex, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))

387
v2/ifd.go
View File

@ -1,402 +1,39 @@
package exif
import (
"errors"
"fmt"
"strings"
"github.com/dsoprea/go-logging"
"github.com/dsoprea/go-exif/v2/common"
)
var (
ifdLogger = log.NewLogger("exif.ifd")
)
var (
ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent")
)
// MappedIfd is one node in the IFD-mapping.
type MappedIfd struct {
ParentTagId uint16
Placement []uint16
Path []string
Name string
TagId uint16
Children map[uint16]*MappedIfd
}
// String returns a descriptive string.
func (mi *MappedIfd) String() string {
pathPhrase := mi.PathPhrase()
return fmt.Sprintf("MappedIfd<(0x%04X) [%s] PATH=[%s]>", mi.TagId, mi.Name, pathPhrase)
}
// PathPhrase returns a non-fully-qualified IFD path.
func (mi *MappedIfd) PathPhrase() string {
return strings.Join(mi.Path, "/")
}
// TODO(dustin): Refactor this to use IfdIdentity structs.
// IfdMapping describes all of the IFDs that we currently recognize.
type IfdMapping struct {
rootNode *MappedIfd
}
// TODO(dustin): This file now exists for backwards-compatibility only.
// NewIfdMapping returns a new IfdMapping struct.
func NewIfdMapping() (ifdMapping *IfdMapping) {
rootNode := &MappedIfd{
Path: make([]string, 0),
Children: make(map[uint16]*MappedIfd),
}
return &IfdMapping{
rootNode: rootNode,
}
//
// RELEASE(dustin): This is a bridging function for backwards-compatibility. Remove this in the next release.
func NewIfdMapping() (ifdMapping *exifcommon.IfdMapping) {
return exifcommon.NewIfdMapping()
}
// NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the
// standard IFDs.
func NewIfdMappingWithStandard() (ifdMapping *IfdMapping) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.Panic(err)
}
}()
im := NewIfdMapping()
err := LoadStandardIfds(im)
log.PanicIf(err)
return im
}
// Get returns the node given the path slice.
func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ptr := im.rootNode
for _, tagId := range parentPlacement {
if descendantPtr, found := ptr.Children[tagId]; found == false {
log.Panicf("ifd child with tag-ID (%04x) not registered: [%s]", tagId, ptr.PathPhrase())
} else {
ptr = descendantPtr
}
}
return ptr, nil
}
// GetWithPath returns the node given the path string.
func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if pathPhrase == "" {
log.Panicf("path-phrase is empty")
}
path := strings.Split(pathPhrase, "/")
ptr := im.rootNode
for _, name := range path {
var hit *MappedIfd
for _, mi := range ptr.Children {
if mi.Name == name {
hit = mi
break
}
}
if hit == nil {
log.Panicf("ifd child with name [%s] not registered: [%s]", name, ptr.PathPhrase())
}
ptr = hit
}
return ptr, nil
}
// GetChild is a convenience function to get the child path for a given parent
// placement and child tag-ID.
func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
mi, err = im.GetWithPath(parentPathPhrase)
log.PanicIf(err)
for _, childMi := range mi.Children {
if childMi.TagId == tagId {
return childMi, nil
}
}
// Whether or not an IFD is defined in data, such an IFD is not registered
// and would be unknown.
log.Panic(ErrChildIfdNotMapped)
return nil, nil
}
// IfdTagIdAndIndex represents a specific part of the IFD path.
//
// This is a legacy type.
type IfdTagIdAndIndex struct {
Name string
TagId uint16
Index int
}
// String returns a descriptive string.
func (itii IfdTagIdAndIndex) String() string {
return fmt.Sprintf("IfdTagIdAndIndex<NAME=[%s] ID=(%04x) INDEX=(%d)>", itii.Name, itii.TagId, itii.Index)
}
// ResolvePath takes a list of names, which can also be suffixed with indices
// (to identify the second, third, etc.. sibling IFD) and returns a list of
// tag-IDs and those indices.
//
// Example:
//
// - IFD/Exif/Iop
// - IFD0/Exif/Iop
//
// This is the only call that supports adding the numeric indices.
func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
pathPhrase = strings.TrimSpace(pathPhrase)
if pathPhrase == "" {
log.Panicf("can not resolve empty path-phrase")
}
path := strings.Split(pathPhrase, "/")
lineage = make([]IfdTagIdAndIndex, len(path))
ptr := im.rootNode
empty := IfdTagIdAndIndex{}
for i, name := range path {
indexByte := name[len(name)-1]
index := 0
if indexByte >= '0' && indexByte <= '9' {
index = int(indexByte - '0')
name = name[:len(name)-1]
}
itii := IfdTagIdAndIndex{}
for _, mi := range ptr.Children {
if mi.Name != name {
continue
}
itii.Name = name
itii.TagId = mi.TagId
itii.Index = index
ptr = mi
break
}
if itii == empty {
log.Panicf("ifd child with name [%s] not registered: [%s]", name, pathPhrase)
}
lineage[i] = itii
}
return lineage, nil
}
// FqPathPhraseFromLineage returns the fully-qualified IFD path from the slice.
func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string) {
fqPathParts := make([]string, len(lineage))
for i, itii := range lineage {
if itii.Index > 0 {
fqPathParts[i] = fmt.Sprintf("%s%d", itii.Name, itii.Index)
} else {
fqPathParts[i] = itii.Name
}
}
return strings.Join(fqPathParts, "/")
}
// PathPhraseFromLineage returns the non-fully-qualified IFD path from the
// slice.
func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string) {
pathParts := make([]string, len(lineage))
for i, itii := range lineage {
pathParts[i] = itii.Name
}
return strings.Join(pathParts, "/")
}
// StripPathPhraseIndices returns a non-fully-qualified path-phrase (no
// indices).
func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
lineage, err := im.ResolvePath(pathPhrase)
log.PanicIf(err)
strippedPathPhrase = im.PathPhraseFromLineage(lineage)
return strippedPathPhrase, nil
}
// Add puts the given IFD at the given position of the tree. The position of the
// tree is referred to as the placement and is represented by a set of tag-IDs,
// where the leftmost is the root tag and the tags going to the right are
// progressive descendants.
func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! It would be nicer to provide a list of names in the placement rather than tag-IDs.
ptr, err := im.Get(parentPlacement)
log.PanicIf(err)
path := make([]string, len(parentPlacement)+1)
if len(parentPlacement) > 0 {
copy(path, ptr.Path)
}
path[len(path)-1] = name
placement := make([]uint16, len(parentPlacement)+1)
if len(placement) > 0 {
copy(placement, ptr.Placement)
}
placement[len(placement)-1] = tagId
childIfd := &MappedIfd{
ParentTagId: ptr.TagId,
Path: path,
Placement: placement,
Name: name,
TagId: tagId,
Children: make(map[uint16]*MappedIfd),
}
if _, found := ptr.Children[tagId]; found == true {
log.Panicf("child IFD with tag-ID (%04x) already registered under IFD [%s] with tag-ID (%04x)", tagId, ptr.Name, ptr.TagId)
}
ptr.Children[tagId] = childIfd
return nil
}
func (im *IfdMapping) dumpLineages(stack []*MappedIfd, input []string) (output []string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
currentIfd := stack[len(stack)-1]
output = input
for _, childIfd := range currentIfd.Children {
stackCopy := make([]*MappedIfd, len(stack)+1)
copy(stackCopy, stack)
stackCopy[len(stack)] = childIfd
// Add to output, but don't include the obligatory root node.
parts := make([]string, len(stackCopy)-1)
for i, mi := range stackCopy[1:] {
parts[i] = mi.Name
}
output = append(output, strings.Join(parts, "/"))
output, err = im.dumpLineages(stackCopy, output)
log.PanicIf(err)
}
return output, nil
}
// DumpLineages returns a slice of strings representing all mappings.
func (im *IfdMapping) DumpLineages() (output []string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
stack := []*MappedIfd{im.rootNode}
output = make([]string, 0)
output, err = im.dumpLineages(stack, output)
log.PanicIf(err)
return output, nil
// RELEASE(dustin): This is a bridging function for backwards-compatibility. Remove this in the next release.
func NewIfdMappingWithStandard() (ifdMapping *exifcommon.IfdMapping) {
return exifcommon.NewIfdMappingWithStandard()
}
// LoadStandardIfds loads the standard IFDs into the mapping.
func LoadStandardIfds(im *IfdMapping) (err error) {
//
// RELEASE(dustin): This is a bridging function for backwards-compatibility. Remove this in the next release.
func LoadStandardIfds(im *exifcommon.IfdMapping) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
err = im.Add(
[]uint16{},
exifcommon.IfdStandardIfdIdentity.TagId(), exifcommon.IfdStandardIfdIdentity.Name())
log.PanicIf(err)
err = im.Add(
[]uint16{exifcommon.IfdStandardIfdIdentity.TagId()},
exifcommon.IfdExifStandardIfdIdentity.TagId(), exifcommon.IfdExifStandardIfdIdentity.Name())
log.PanicIf(err)
err = im.Add(
[]uint16{exifcommon.IfdStandardIfdIdentity.TagId(), exifcommon.IfdExifStandardIfdIdentity.TagId()},
exifcommon.IfdExifIopStandardIfdIdentity.TagId(), exifcommon.IfdExifIopStandardIfdIdentity.Name())
log.PanicIf(err)
err = im.Add(
[]uint16{exifcommon.IfdStandardIfdIdentity.TagId()},
exifcommon.IfdGpsInfoStandardIfdIdentity.TagId(), exifcommon.IfdGpsInfoStandardIfdIdentity.Name())
err = exifcommon.LoadStandardIfds(im)
log.PanicIf(err)
return nil

View File

@ -238,11 +238,11 @@ type IfdBuilder struct {
// data. Otherwise, it's nil.
thumbnailData []byte
ifdMapping *IfdMapping
ifdMapping *exifcommon.IfdMapping
tagIndex *TagIndex
}
func NewIfdBuilder(ifdMapping *IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
func NewIfdBuilder(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
ib = &IfdBuilder{
ifdIdentity: ii,
@ -332,7 +332,7 @@ func (ib *IfdBuilder) ChildWithTagId(childIfdTagId uint16) (childIb *IfdBuilder,
return nil, nil
}
func getOrCreateIbFromRootIbInner(rootIb *IfdBuilder, parentIb *IfdBuilder, currentLineage []IfdTagIdAndIndex) (ib *IfdBuilder, err error) {
func getOrCreateIbFromRootIbInner(rootIb *IfdBuilder, parentIb *IfdBuilder, currentLineage []exifcommon.IfdTagIdAndIndex) (ib *IfdBuilder, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -346,7 +346,7 @@ func getOrCreateIbFromRootIbInner(rootIb *IfdBuilder, parentIb *IfdBuilder, curr
// Since we're calling ourselves recursively with incrementally different
// paths, the FQ IFD-path of the parent that called us needs to be passed
// in, in order for us to know it.
var parentLineage []IfdTagIdAndIndex
var parentLineage []exifcommon.IfdTagIdAndIndex
if parentIb != nil {
var err error
@ -555,7 +555,7 @@ func (ib *IfdBuilder) printTagTree(levels int) {
_, err := ib.ifdMapping.GetChild(currentIb.IfdIdentity().UnindexedString(), tag.tagId)
if err == nil {
isChildIb = true
} else if log.Is(err, ErrChildIfdNotMapped) == false {
} else if log.Is(err, exifcommon.ErrChildIfdNotMapped) == false {
log.Panic(err)
}
@ -624,7 +624,7 @@ func (ib *IfdBuilder) printIfdTree(levels int) {
_, err := ib.ifdMapping.GetChild(currentIb.IfdIdentity().UnindexedString(), tag.tagId)
if err == nil {
isChildIb = true
} else if log.Is(err, ErrChildIfdNotMapped) == false {
} else if log.Is(err, exifcommon.ErrChildIfdNotMapped) == false {
log.Panic(err)
}

View File

@ -171,12 +171,12 @@ type IfdEnumerate struct {
buffer *bytes.Buffer
byteOrder binary.ByteOrder
tagIndex *TagIndex
ifdMapping *IfdMapping
ifdMapping *exifcommon.IfdMapping
furthestOffset uint32
}
// NewIfdEnumerate returns a new instance of IfdEnumerate.
func NewIfdEnumerate(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate {
func NewIfdEnumerate(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate {
return &IfdEnumerate{
exifData: exifData,
buffer: bytes.NewBuffer(exifData),
@ -266,7 +266,7 @@ func (ie *IfdEnumerate) parseTag(ii *exifcommon.IfdIdentity, tagPosition int, bp
// We also need to set `tag.ChildFqIfdPath` but can't do it here
// because we don't have the IFD index.
} else if log.Is(err, ErrChildIfdNotMapped) == false {
} else if log.Is(err, exifcommon.ErrChildIfdNotMapped) == false {
log.Panic(err)
}
@ -656,7 +656,7 @@ type Ifd struct {
thumbnailData []byte
ifdMapping *IfdMapping
ifdMapping *exifcommon.IfdMapping
tagIndex *TagIndex
}
@ -1387,7 +1387,7 @@ func (ie *IfdEnumerate) FurthestOffset() uint32 {
// in that the numeric index will always be zero (the zeroth child) rather than
// the proper number (if its actually a sibling to the first child, for
// instance).
func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitorFn) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
func ParseOneIfd(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitorFn) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -1412,7 +1412,7 @@ func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdI
}
// ParseOneTag is a hack to use an IE to parse a raw tag block.
func ParseOneTag(ifdMapping *IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (ite *IfdTagEntry, err error) {
func ParseOneTag(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (ite *IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))