excel.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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. return err
  189. } else {
  190. fmt.Printf("Found %d formulas in the sheet.\n", len(formulas))
  191. }
  192. fmt.Println("\nSample data (first 5 rows):")
  193. rowCount := 5
  194. if len(data.Rows) < rowCount {
  195. rowCount = len(data.Rows)
  196. }
  197. for i := 0; i < rowCount; i++ {
  198. fmt.Printf("Row %d:\n", i+1)
  199. row := data.Rows[i]
  200. for j, cell := range row {
  201. // fmt.Println("i: ", i)
  202. // fmt.Println("j: ", j)
  203. // //fmt.Println("data.Headers[j]: ", data.Headers[j])
  204. // fmt.Println("cell: ", cell)
  205. if i == 1 {
  206. formula, ok:= formulas[i][j]
  207. if ok {
  208. fmt.Printf(" %s formula: %s\n", data.Headers[j], formula)
  209. // 解析公式
  210. ParseSpecificKplerFormulaV2(formula)
  211. }
  212. }
  213. if j < len(data.Headers) && cell != "" {
  214. fmt.Printf(" %s: %s\n", data.Headers[j], cell)
  215. }
  216. }
  217. fmt.Println()
  218. }
  219. // Print total number of data rows
  220. fmt.Printf("Total data rows: %d\n", len(data.Rows))
  221. return nil
  222. }
  223. // 旧版本的kpler插件生成的excel文件解析
  224. func ParseSpecificKplerFormulaV1() {
  225. // The specific formula provided in the user's question
  226. 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",)`
  227. fmt.Println("Analyzing the specific Kpler formula from the user's question:")
  228. fmt.Println(specificFormula)
  229. parsedFormula, err := ParseKplerFormula(specificFormula)
  230. if err != nil {
  231. fmt.Printf("Error parsing formula: %v\n", err)
  232. return
  233. }
  234. fmt.Println("\nFormula breakdown:")
  235. fmt.Printf("Function name: %s\n", parsedFormula.Function)
  236. fmt.Println("Parameters list:")
  237. // Define parameter names for GetFlows function based on Kpler documentation
  238. paramNames := []string{
  239. "fromZones", // 1. "Saudi Arabia,"
  240. "toZones", // 2. empty
  241. "fromInstallations", // 3. empty
  242. "toInstallations", // 4. empty
  243. "fromRegions", // 5. empty
  244. "toRegions", // 6. empty
  245. "flowDirection", // 7. "import"
  246. "granularity", // 8. "weekly"
  247. "split", // 9. "Total"
  248. "unit", // 10. "kbd"
  249. "withForecast", // 14. FALSE
  250. "products",
  251. "withProductEstimation", // 15. FALSE
  252. "withIntraCountry", // 16. FALSE
  253. "withIntraRegion", // 17. TRUE
  254. "withFreightView", // false
  255. "withPeriodEndTime", // false
  256. "productFilter", // 18. "CPP"
  257. "lastDataPoints", // 19. empty
  258. }
  259. // Print each parameter with its meaning
  260. for i, param := range parsedFormula.Parameters {
  261. if i < len(paramNames) {
  262. paramName := paramNames[i]
  263. if param == "" {
  264. fmt.Printf(" %s: [empty]\n", paramName)
  265. } else {
  266. fmt.Printf(" %s: %s\n", paramName, param)
  267. }
  268. } else {
  269. fmt.Printf(" Parameter %d: %s\n", i+1, param)
  270. }
  271. }
  272. }
  273. // Main function for standalone testing
  274. func GetKplerDataByExcel() {
  275. //fmt.Println("Testing Kpler formula parsing...")
  276. // FormulaExample()
  277. fmt.Println("Starting Kpler data processing...")
  278. //ParseSpecificKplerFormulaV2()
  279. // First demonstrate the specific formula parsing
  280. // ParseSpecificKplerFormula()
  281. // Then process the Excel data
  282. err := ProcessKplerData()
  283. if err != nil {
  284. fmt.Println("error processing Excel data: %w", err)
  285. }
  286. fmt.Println("Kpler data processing completed successfully!")
  287. }
  288. func ParseSpecificKplerFormulaV2(specificFormula string) {
  289. specificFormula = strings.ReplaceAll(specificFormula, `"&"`, "")
  290. //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")`
  291. // 吧函数的入参解析成json串,并转成结构体
  292. // 手动解码URL编码字符串以获取JSON
  293. // 这是完整的公式参数 - Excel中使用"&"连接字符串
  294. //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`
  295. // 获取括号里的内容
  296. re := regexp.MustCompile(`\((.*)\)`)
  297. matches := re.FindStringSubmatch(specificFormula)
  298. if len(matches) < 2 {
  299. fmt.Println("没有找到括号里的内容")
  300. return
  301. }
  302. encodedParam := matches[1]
  303. fmt.Println("encodedParam: ", encodedParam)
  304. // 解码URL编码的字符串
  305. decodedStr, err := url.QueryUnescape(encodedParam)
  306. if err != nil {
  307. fmt.Printf("Error decoding URL: %v\n", err)
  308. return
  309. } else {
  310. // 打印解码后的JSON字符串
  311. fmt.Println("Decoded parameter JSON:")
  312. fmt.Println(decodedStr)
  313. // 使解码后的JSON更易读
  314. var jsonObj interface{}
  315. if err := json.Unmarshal([]byte(decodedStr), &jsonObj); err == nil {
  316. prettyJSON, _ := json.MarshalIndent(jsonObj, "", " ")
  317. fmt.Println("\nPretty JSON format:")
  318. fmt.Println(string(prettyJSON))
  319. } else {
  320. fmt.Printf("Error parsing JSON: %v\n", err)
  321. return
  322. }
  323. }
  324. }