Skip to content

Commit

Permalink
Merge branch 'elimity-com-hyperlinks'
Browse files Browse the repository at this point in the history
  • Loading branch information
tealeg committed Oct 30, 2019
2 parents 7e2c451 + 5100882 commit 192f270
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ xlsx.test
*.swp
coverage.txt
.idea

24 changes: 24 additions & 0 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ type Cell struct {
VMerge int
cellType CellType
DataValidation *xlsxDataValidation
Hyperlink Hyperlink
}

type Hyperlink struct {
DisplayString string
Link string
Tooltip string
}

// CellInterface defines the public API of the Cell.
Expand Down Expand Up @@ -242,6 +249,23 @@ func (c *Cell) SetInt(n int) {
c.SetValue(n)
}

// SetHyperlink sets this cell to contain the given hyperlink, displayText and tooltip.
// If the displayText or tooltip are an empty string, they will not be set.
// The hyperlink provided must be a valid URL starting with http:// or https:// or
// excel will not recognize it as an external link.
func (c *Cell) SetHyperlink(hyperlink string, displayText string, tooltip string) {
c.Hyperlink = Hyperlink{Link: hyperlink}
c.SetString(hyperlink)
c.Row.Sheet.addRelation(RelationshipTypeHyperlink, hyperlink, RelationshipTargetModeExternal)
if displayText != "" {
c.Hyperlink.DisplayString = displayText
c.SetString(displayText)
}
if tooltip != "" {
c.Hyperlink.Tooltip = tooltip
}
}

// SetInt sets a cell's value to an integer.
func (c *Cell) SetValue(n interface{}) {
switch t := n.(type) {
Expand Down
27 changes: 25 additions & 2 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
// to the user.
type File struct {
worksheets map[string]*zip.File
worksheetRels map[string]*zip.File
referenceTable *RefTable
Date1904 bool
styles *xlsxStyleSheet
Expand Down Expand Up @@ -239,6 +240,17 @@ func replaceRelationshipsNameSpace(workbookMarshal string) string {
return strings.Replace(newWorkbook, oldXmlns, newXmlns, 1)
}

func addRelationshipNameSpaceToWorksheet(worksheetMarshal string) string {
oldXmlns := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
newXmlns := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">`
newSheetMarshall := strings.Replace(worksheetMarshal, oldXmlns, newXmlns, 1)

oldHyperlink := `<hyperlink id=`
newHyperlink := `<hyperlink r:id=`
newSheetMarshall = strings.Replace(newSheetMarshall, oldHyperlink, newHyperlink, -1)
return newSheetMarshall
}

// Construct a map of file name to XML content representing the file
// in terms of the structure of an XLSX file.
func (f *File) MarshallParts() (map[string]string, error) {
Expand Down Expand Up @@ -271,11 +283,13 @@ func (f *File) MarshallParts() (map[string]string, error) {
return nil, err
}
for _, sheet := range f.Sheets {
xSheet := sheet.makeXLSXSheet(refTable, f.styles)
xSheetRels := sheet.makeXLSXSheetRelations()
xSheet := sheet.makeXLSXSheet(refTable, f.styles, xSheetRels)
rId := fmt.Sprintf("rId%d", sheetIndex)
sheetId := strconv.Itoa(sheetIndex)
sheetPath := fmt.Sprintf("worksheets/sheet%d.xml", sheetIndex)
partName := "xl/" + sheetPath
relPartName := fmt.Sprintf("xl/worksheets/_rels/sheet%d.xml.rels", sheetIndex)
types.Overrides = append(
types.Overrides,
xlsxOverride{
Expand All @@ -287,10 +301,19 @@ func (f *File) MarshallParts() (map[string]string, error) {
SheetId: sheetId,
Id: rId,
State: "visible"}
parts[partName], err = marshal(xSheet)

worksheetMarshal, err := marshal(xSheet)
if err != nil {
return parts, err
}
worksheetMarshal = addRelationshipNameSpaceToWorksheet(worksheetMarshal)
parts[partName] = worksheetMarshal
if xSheetRels != nil {
parts[relPartName], err = marshal(xSheetRels)
if err != nil {
return parts, err
}
}
sheetIndex++
}

Expand Down
61 changes: 57 additions & 4 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,13 +470,11 @@ func (l *FileSuite) TestMarshalFile(c *C) {

// sheets
expectedSheet1 := `<?xml version="1.0" encoding="UTF-8"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><sheetData><row r="1"><c r="A1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`

<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><sheetData><row r="1"><c r="A1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
c.Assert(parts["xl/worksheets/sheet1.xml"], Equals, expectedSheet1)

expectedSheet2 := `<?xml version="1.0" encoding="UTF-8"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="false" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><sheetData><row r="1"><c r="A1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`

<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="false" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><sheetData><row r="1"><c r="A1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
c.Assert(parts["xl/worksheets/sheet2.xml"], Equals, expectedSheet2)

// .rels.xml
Expand Down Expand Up @@ -887,6 +885,61 @@ func (l *FileSuite) TestSaveFile(c *C) {
c.Assert(cell1.Value, Equals, "A cell!")
}

func (l *FileSuite) TestMarshalFileWithHyperlinks(c *C) {
var f *File
f = NewFile()
sheet1, _ := f.AddSheet("MySheet")
row1 := sheet1.AddRow()
cell1 := row1.AddCell()
cell1.SetString("A cell!")
cell1.SetHyperlink("http://www.google.com", "", "")
c.Assert(cell1.Value, Equals, "http://www.google.com")
sheet2, _ := f.AddSheet("AnotherSheet")
row2 := sheet2.AddRow()
cell2 := row2.AddCell()
cell2.SetString("A cell!")
cell2.SetHyperlink("http://www.google.com/index.html", "This is a hyperlink", "Click on the cell text to follow the hyperlink")
c.Assert(cell2.Value, Equals, "This is a hyperlink")
parts, err := f.MarshallParts()
c.Assert(err, IsNil)
c.Assert(len(parts), Equals, 13)
}

// We can save a File as a valid XLSX file at a given path.
func (l *FileSuite) TestSaveFileWithHyperlinks(c *C) {
var tmpPath string = c.MkDir()
var f *File
f = NewFile()
sheet1, _ := f.AddSheet("MySheet")
row1 := sheet1.AddRow()
cell1 := row1.AddCell()
cell1.SetString("A cell!")
cell1.SetHyperlink("http://www.google.com", "", "")
c.Assert(cell1.Value, Equals, "http://www.google.com")
sheet2, _ := f.AddSheet("AnotherSheet")
row2 := sheet2.AddRow()
cell2 := row2.AddCell()
cell2.SetString("A cell!")
cell2.SetHyperlink("http://www.google.com/index.html", "This is a hyperlink", "Click on the cell text to follow the hyperlink")
c.Assert(cell2.Value, Equals, "This is a hyperlink")
xlsxPath := filepath.Join(tmpPath, "TestSaveFile.xlsx")
err := f.Save(xlsxPath)
c.Assert(err, IsNil)

xlsxFile, err := OpenFile(xlsxPath)
c.Assert(err, IsNil)
c.Assert(xlsxFile, NotNil)
c.Assert(len(xlsxFile.Sheets), Equals, 2)

sheet1, ok := xlsxFile.Sheet["MySheet"]
c.Assert(ok, Equals, true)
c.Assert(len(sheet1.Rows), Equals, 1)
row1 = sheet1.Rows[0]
c.Assert(len(row1.Cells), Equals, 1)
cell1 = row1.Cells[0]
c.Assert(cell1.Value, Equals, "http://www.google.com")
}

type SliceReaderSuite struct{}

var _ = Suite(&SliceReaderSuite{})
Expand Down
56 changes: 55 additions & 1 deletion lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,52 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
sheet.AutoFilter = &AutoFilter{autoFilterBounds[0], autoFilterBounds[1]}
}

// Convert xlsxHyperlinks to Hyperlinks
if worksheet.Hyperlinks != nil {

worksheetRelsFile := fi.worksheetRels["sheet"+rsheet.SheetId]
worksheetRels := new(xlsxWorksheetRels)
rc, err := worksheetRelsFile.Open()
if err != nil {
return err
}
decoder := xml.NewDecoder(rc)
err = decoder.Decode(worksheetRels)
if err != nil {
return err
}

for _, xlsxLink := range worksheet.Hyperlinks.HyperLinks {
newHyperLink := Hyperlink{}

relationPresent := false
for _, rel := range worksheetRels.Relationships {
if rel.Id == xlsxLink.RelationshipId {
newHyperLink.Link = rel.Target
relationPresent = true
break
}
}
if !relationPresent {
return errors.New("sheets relations file has no relations for the relation id present in the hyperlink")
}

if xlsxLink.Tooltip != "" {
newHyperLink.Tooltip = xlsxLink.Tooltip
}
if xlsxLink.DisplayString != "" {
newHyperLink.DisplayString = xlsxLink.DisplayString
}
cellRef := xlsxLink.Reference
x, y, err := GetCoordsFromCellIDString(cellRef)
if err != nil {
return err
}
cell := sheet.Row(y).Cells[x]
cell.Hyperlink = newHyperLink
}
}

sheet.SheetFormat.DefaultColWidth = worksheet.SheetFormatPr.DefaultColWidth
sheet.SheetFormat.DefaultRowHeight = worksheet.SheetFormatPr.DefaultRowHeight
sheet.SheetFormat.OutlineLevelCol = worksheet.SheetFormatPr.OutlineLevelCol
Expand Down Expand Up @@ -983,10 +1029,12 @@ func ReadZipReaderWithRowLimit(r *zip.Reader, rowLimit int) (*File, error) {
var workbook *zip.File
var workbookRels *zip.File
var worksheets map[string]*zip.File
var worksheetRels map[string]*zip.File

file = NewFile()
// file.numFmtRefTable = make(map[int]xlsxNumFmt, 1)
worksheets = make(map[string]*zip.File, len(r.File))
worksheetRels = make(map[string]*zip.File, len(r.File))
for _, v = range r.File {
switch v.Name {
case "xl/sharedStrings.xml":
Expand All @@ -1002,7 +1050,11 @@ func ReadZipReaderWithRowLimit(r *zip.Reader, rowLimit int) (*File, error) {
default:
if len(v.Name) > 17 {
if v.Name[0:13] == "xl/worksheets" {
worksheets[v.Name[14:len(v.Name)-4]] = v
if v.Name[len(v.Name)-5:] == ".rels" {
worksheetRels[v.Name[20:len(v.Name)-9]] = v
} else {
worksheets[v.Name[14:len(v.Name)-4]] = v
}
}
}
}
Expand All @@ -1018,6 +1070,7 @@ func ReadZipReaderWithRowLimit(r *zip.Reader, rowLimit int) (*File, error) {
return nil, fmt.Errorf("Input xlsx contains no worksheets.")
}
file.worksheets = worksheets
file.worksheetRels = worksheetRels
reftable, err = readSharedStringsFromZipFile(sharedStrings)
if err != nil {
return nil, err
Expand All @@ -1040,6 +1093,7 @@ func ReadZipReaderWithRowLimit(r *zip.Reader, rowLimit int) (*File, error) {
file.styles = style
}
sheetsByName, sheets, err = readSheetsFromZipFile(workbook, file, sheetXMLMap, rowLimit)
//sheetRelsByName, sheetRels, err = readSheetRelationsFromZipFile()
if err != nil {
return nil, err
}
Expand Down
10 changes: 10 additions & 0 deletions lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ func (l *LibSuite) TestReadZipReaderWithFileWithNoWorksheets(c *C) {
c.Assert(err.Error(), Equals, "Input xlsx contains no worksheets.")
}

// Read a file containing hyperlinks in cells
func (l *LibSuite) TestReadFileWithHyperlinks(c *C) {
file, err := OpenFile("./testdocs/file_with_hyperlinks.xlsx")
if err != nil {
c.Failed()
}
c.Assert(file.Sheets[0].Row(0).Cells[0].Hyperlink, Equals, Hyperlink{Link:"https://www.google.com/"})
c.Assert(file.Sheets[0].Row(1).Cells[0].Hyperlink, Equals, Hyperlink{Link:"https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/cc802445(v%3Doffice.14)"})
}

// Attempt to read data from a file with inlined string sheet data.
func (l *LibSuite) TestReadWithInlineStrings(c *C) {
var xlsxFile *File
Expand Down
Loading

0 comments on commit 192f270

Please sign in to comment.