excel.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. package kpler
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/url"
  6. "regexp"
  7. "strings"
  8. "github.com/xuri/excelize/v2"
  9. )
  10. // ExcelData represents structured data extracted from an Excel file
  11. type ExcelData struct {
  12. Headers []string
  13. Rows [][]string
  14. SheetName string
  15. }
  16. // KplerFormulaData represents parsed data from a Kpler formula
  17. type KplerFormulaData struct {
  18. Function string
  19. Parameters []string
  20. FullFormula string
  21. }
  22. // ParseExcel reads and parses data from an Excel file
  23. func ParseExcel(filePath string) (*ExcelData, error) {
  24. // Open the Excel file
  25. f, err := excelize.OpenFile(filePath)
  26. if err != nil {
  27. return nil, fmt.Errorf("error opening Excel file: %w", err)
  28. }
  29. defer f.Close()
  30. // Get the first sheet by default
  31. sheetName := f.GetSheetList()[0]
  32. // Get all rows from the sheet
  33. rows, err := f.GetRows(sheetName)
  34. if err != nil {
  35. return nil, fmt.Errorf("error reading rows from sheet %s: %w", sheetName, err)
  36. }
  37. // Check if there's data
  38. if len(rows) == 0 {
  39. return nil, fmt.Errorf("no data found in sheet %s", sheetName)
  40. }
  41. // Create structured data
  42. excelData := &ExcelData{
  43. SheetName: sheetName,
  44. Headers: rows[0],
  45. Rows: rows[1:],
  46. }
  47. return excelData, nil
  48. }
  49. // ParseKplerFormula extracts the function name and parameters from a Kpler Excel formula
  50. func ParseKplerFormula(formula string) (*KplerFormulaData, error) {
  51. result := &KplerFormulaData{
  52. FullFormula: formula,
  53. }
  54. // Regular expression to match the function name and parameters
  55. // This pattern looks for: =@'path\to\file.xlam'!FunctionName(param1,param2,...)
  56. re := regexp.MustCompile(`=@'[^']*'!([A-Za-z0-9_]+)\((.*)\)`)
  57. matches := re.FindStringSubmatch(formula)
  58. if len(matches) < 3 {
  59. // Try another pattern without the @' prefix
  60. re = regexp.MustCompile(`=([A-Za-z0-9_]+)\((.*)\)`)
  61. matches = re.FindStringSubmatch(formula)
  62. if len(matches) < 3 {
  63. return nil, fmt.Errorf("could not parse Kpler formula: %s", formula)
  64. }
  65. }
  66. // Extract function name
  67. result.Function = matches[1]
  68. // Extract parameters
  69. paramsStr := matches[2]
  70. // Split parameters, handling commas inside quotes
  71. var params []string
  72. inQuote := false
  73. currentParam := ""
  74. for _, char := range paramsStr {
  75. switch char {
  76. case '"':
  77. inQuote = !inQuote
  78. currentParam += string(char)
  79. case ',':
  80. if inQuote {
  81. currentParam += string(char)
  82. } else {
  83. params = append(params, strings.TrimSpace(currentParam))
  84. currentParam = ""
  85. }
  86. default:
  87. currentParam += string(char)
  88. }
  89. }
  90. // Add the last parameter
  91. if currentParam != "" {
  92. params = append(params, strings.TrimSpace(currentParam))
  93. }
  94. result.Parameters = params
  95. return result, nil
  96. }
  97. // ScanSheetForFormulas scans an entire sheet for formulas
  98. func ScanSheetForFormulas(filePath, sheetName string) (map[int]map[int]string, error) {
  99. // Open the Excel file
  100. f, err := excelize.OpenFile(filePath)
  101. if err != nil {
  102. return nil, fmt.Errorf("error opening Excel file: %w", err)
  103. }
  104. defer f.Close()
  105. formulas := make(map[int]map[int]string)
  106. // Get sheet dimensions
  107. dimension, err := f.GetSheetDimension(sheetName)
  108. if err != nil {
  109. return nil, fmt.Errorf("error getting sheet dimension: %w", err)
  110. }
  111. // Parse dimension to get the range (e.g., "A1:K42")
  112. parts := strings.Split(dimension, ":")
  113. if len(parts) != 2 {
  114. // Use a default range if dimension is not in expected format
  115. parts = []string{"A1", "Z100"}
  116. }
  117. // Extract the column letters and row numbers
  118. startCol, startRow, err := excelize.CellNameToCoordinates(parts[0])
  119. if err != nil {
  120. startCol, startRow = 1, 1
  121. }
  122. endCol, endRow, err := excelize.CellNameToCoordinates(parts[1])
  123. if err != nil {
  124. endCol, endRow = 26, 100 // Default to Z100
  125. }
  126. // Scan cells for formulas
  127. for row := startRow; row <= endRow; row++ {
  128. for col := startCol; col <= endCol; col++ {
  129. colName, err := excelize.ColumnNumberToName(col)
  130. if err != nil {
  131. continue
  132. }
  133. cellCoord := fmt.Sprintf("%s%d", colName, row)
  134. formula, err := f.GetCellFormula(sheetName, cellCoord)
  135. if err == nil && formula != "" {
  136. // Store all formulas or only Kpler-related ones (adjust as needed)
  137. //if strings.Contains(formula, "kpler") || strings.Contains(formula, "GetFlows") {
  138. if strings.Contains(formula, "GETFLOWS") {
  139. // fmt.Println("row: ", row)
  140. // fmt.Println("col: ", col)
  141. // fmt.Println("GetCellFormula: ", formula)
  142. if _, ok := formulas[row-1]; !ok {
  143. formulas[row-1] = make(map[int]string)
  144. }
  145. formulas[row-1][col-1] = formula
  146. }
  147. }
  148. }
  149. }
  150. return formulas, nil
  151. }
  152. // ProcessKplerData demonstrates how to parse and process Kpler crude flow data
  153. func ProcessKplerData() error {
  154. // Path to the Kpler crude flow Excel file
  155. //filePath := "services/kpler/Kpler crude flow (自动保存的).xlsx"
  156. filePath := "services/kpler/最新版kpler插件.xlsx"
  157. // First, list all sheets in the Excel file
  158. // Open the Excel file
  159. f, err := excelize.OpenFile(filePath)
  160. if err != nil {
  161. return fmt.Errorf("error opening Excel file: %w", err)
  162. }
  163. // Return the list of sheets
  164. sheets := f.GetSheetList()
  165. if err != nil {
  166. return fmt.Errorf("error listing sheets: %w", err)
  167. }
  168. defer f.Close()
  169. fmt.Println("Available sheets:")
  170. for i, sheet := range sheets {
  171. fmt.Printf("%d. %s\n", i+1, sheet)
  172. }
  173. // Parse the Excel file using the first sheet (default)
  174. data, err := ParseExcel(filePath)
  175. if err != nil {
  176. return fmt.Errorf("error parsing Excel file: %w", err)
  177. }
  178. // Print the headers and a sample of data rows
  179. // fmt.Println("\nHeaders found in the sheet:")
  180. // for i, header := range data.Headers {
  181. // fmt.Printf("%d. %s\n", i+1, header)
  182. // }
  183. // Look for Kpler formulas
  184. fmt.Println("\nLooking for Kpler formulas across the sheet...")
  185. formulas, err := ScanSheetForFormulas(filePath, data.SheetName)
  186. if err != nil {
  187. fmt.Printf("Error scanning for formulas: %v\n", err)
  188. } else {
  189. fmt.Printf("Found %d formulas in the sheet.\n", len(formulas))
  190. }
  191. fmt.Println("\nSample data (first 5 rows):")
  192. rowCount := 5
  193. if len(data.Rows) < rowCount {
  194. rowCount = len(data.Rows)
  195. }
  196. for i := 0; i < rowCount; i++ {
  197. fmt.Printf("Row %d:\n", i+1)
  198. row := data.Rows[i]
  199. for j, cell := range row {
  200. // fmt.Println("i: ", i)
  201. // fmt.Println("j: ", j)
  202. // //fmt.Println("data.Headers[j]: ", data.Headers[j])
  203. // fmt.Println("cell: ", cell)
  204. if i == 1 {
  205. formula, ok:= formulas[i][j]
  206. if ok {
  207. fmt.Printf(" %s formula: %s\n", data.Headers[j], formula)
  208. // 解析公式
  209. ParseSpecificKplerFormulaV2(formula)
  210. }
  211. }
  212. if j < len(data.Headers) && cell != "" {
  213. fmt.Printf(" %s: %s\n", data.Headers[j], cell)
  214. }
  215. }
  216. fmt.Println()
  217. }
  218. // Print total number of data rows
  219. fmt.Printf("Total data rows: %d\n", len(data.Rows))
  220. return nil
  221. }
  222. // ParseSpecificKplerFormula parses the specific Kpler formula provided by the user
  223. func ParseSpecificKplerFormulaV1() {
  224. // The specific formula provided in the user's question
  225. specificFormula := `=@'F:\Desktop\kpler\kpler-excel-addin.xlam'!GetFlows("China,","Russian Federation,Venezuela,Iran,EOPL,",,,,,"import","weekly","Total","kbd",FALSE,,FALSE,FALSE,FALSE,FALSE,TRUE,"CPP",)`
  226. fmt.Println("Analyzing the specific Kpler formula from the user's question:")
  227. fmt.Println(specificFormula)
  228. parsedFormula, err := ParseKplerFormula(specificFormula)
  229. if err != nil {
  230. fmt.Printf("Error parsing formula: %v\n", err)
  231. return
  232. }
  233. fmt.Println("\nFormula breakdown:")
  234. fmt.Printf("Function name: %s\n", parsedFormula.Function)
  235. fmt.Println("Parameters list:")
  236. // Define parameter names for GetFlows function based on Kpler documentation
  237. paramNames := []string{
  238. "fromZones", // 1. "Saudi Arabia,"
  239. "toZones", // 2. empty
  240. "fromInstallations", // 3. empty
  241. "toInstallations", // 4. empty
  242. "fromRegions", // 5. empty
  243. "toRegions", // 6. empty
  244. "flowDirection", // 7. "import"
  245. "granularity", // 8. "weekly"
  246. "split", // 9. "Total"
  247. "unit", // 10. "kbd"
  248. "withForecast", // 14. FALSE
  249. "products",
  250. "withProductEstimation", // 15. FALSE
  251. "withIntraCountry", // 16. FALSE
  252. "withIntraRegion", // 17. TRUE
  253. "withFreightView", // false
  254. "withPeriodEndTime", // false
  255. "productFilter", // 18. "CPP"
  256. "lastDataPoints", // 19. empty
  257. }
  258. // Print each parameter with its meaning
  259. for i, param := range parsedFormula.Parameters {
  260. if i < len(paramNames) {
  261. paramName := paramNames[i]
  262. if param == "" {
  263. fmt.Printf(" %s: [empty]\n", paramName)
  264. } else {
  265. fmt.Printf(" %s: %s\n", paramName, param)
  266. }
  267. } else {
  268. fmt.Printf(" Parameter %d: %s\n", i+1, param)
  269. }
  270. }
  271. }
  272. // Main function for standalone testing
  273. func GetKplerDataByExcel() {
  274. //fmt.Println("Testing Kpler formula parsing...")
  275. // FormulaExample()
  276. fmt.Println("Starting Kpler data processing...")
  277. //ParseSpecificKplerFormulaV2()
  278. // First demonstrate the specific formula parsing
  279. // ParseSpecificKplerFormula()
  280. // Then process the Excel data
  281. err := ProcessKplerData()
  282. if err != nil {
  283. fmt.Println("error processing Excel data: %w", err)
  284. }
  285. fmt.Println("Kpler data processing completed successfully!")
  286. }
  287. func ParseSpecificKplerFormulaV2(specificFormula string) {
  288. specificFormula = strings.ReplaceAll(specificFormula, `"&"`, "")
  289. //specificFormula := `=Kpler.getFlows("%7B%22platform%22%3A%22liquids%22%2C%22origins%22%3A%5B%7B%22id%22%3A%22Angola%22%2C%22name%22%3A%22Angola%22%7D%5D%2C%22destinations%22%3A%5B%5D%2C%22fromInstallations%22%3A%5B%5D%2C%22toInstallations%22%3A%5B%5D%2C%22flowDirection%22%3A%22export%22%2C%2" &amp; "2products%22%3A%5B%7B%22id%22%3A%22Crude%22%2C%22name%22%3A%22Crude%22%7D%5D%2C%22unit%22%3A%22kbd%22%2C%22isProductEstimation%22%3Afalse%2C%22isIntracountry%22%3Afalse%2C%22isIntraRegion%22%3Afalse%2C%22isWithForecast%22%3Afalse%2C%22granularity%22%3A%22" &amp; "weeks%22%2C%22vesselClassification%22%3A%22CPP%22%2C%22vesselsTypes%22%3A%5B%5D%2C%22split%22%3A%22Total%22%2C%22isFreightView%22%3Afalse%2C%22isWithPeriodEndTime%22%3Atrue%2C%22projection%22%3A%22actual%22%2C%22selectedPreset%22%3A%223m%22%2C%22startDate" &amp; "%22%3Anull%2C%22endDate%22%3Anull%7D")`
  290. // 吧函数的入参解析成json串,并转成结构体
  291. // 手动解码URL编码字符串以获取JSON
  292. // 这是完整的公式参数 - Excel中使用"&"连接字符串
  293. //encodedParam := `%7B%22platform%22%3A%22liquids%22%2C%22origins%22%3A%5B%7B%22id%22%3A%22Angola%22%2C%22name%22%3A%22Angola%22%7D%5D%2C%22destinations%22%3A%5B%5D%2C%22fromInstallations%22%3A%5B%5D%2C%22toInstallations%22%3A%5B%5D%2C%22flowDirection%22%3A%22export%22%2C%22products%22%3A%5B%7B%22id%22%3A%22Crude%22%2C%22name%22%3A%22Crude%22%7D%5D%2C%22unit%22%3A%22kbd%22%2C%22isProductEstimation%22%3Afalse%2C%22isIntracountry%22%3Afalse%2C%22isIntraRegion%22%3Afalse%2C%22isWithForecast%22%3Afalse%2C%22granularity%22%3A%22weeks%22%2C%22vesselClassification%22%3A%22CPP%22%2C%22vesselsTypes%22%3A%5B%5D%2C%22split%22%3A%22Total%22%2C%22isFreightView%22%3Afalse%2C%22isWithPeriodEndTime%22%3Atrue%2C%22projection%22%3A%22actual%22%2C%22selectedPreset%22%3A%223m%22%2C%22startDate%22%3Anull%2C%22endDate%22%3Anull%7D`
  294. // 获取括号里的内容
  295. re := regexp.MustCompile(`\((.*)\)`)
  296. matches := re.FindStringSubmatch(specificFormula)
  297. if len(matches) < 2 {
  298. fmt.Println("没有找到括号里的内容")
  299. return
  300. }
  301. encodedParam := matches[1]
  302. fmt.Println("encodedParam: ", encodedParam)
  303. // 解码URL编码的字符串
  304. decodedStr, err := url.QueryUnescape(encodedParam)
  305. if err != nil {
  306. fmt.Printf("Error decoding URL: %v\n", err)
  307. return
  308. } else {
  309. // 打印解码后的JSON字符串
  310. fmt.Println("Decoded parameter JSON:")
  311. fmt.Println(decodedStr)
  312. // 使解码后的JSON更易读
  313. var jsonObj interface{}
  314. if err := json.Unmarshal([]byte(decodedStr), &jsonObj); err == nil {
  315. prettyJSON, _ := json.MarshalIndent(jsonObj, "", " ")
  316. fmt.Println("\nPretty JSON format:")
  317. fmt.Println(string(prettyJSON))
  318. } else {
  319. fmt.Printf("Error parsing JSON: %v\n", err)
  320. return
  321. }
  322. }
  323. }