av.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. // SiYuan - Refactor your thinking
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package sql
  17. import (
  18. "bytes"
  19. "fmt"
  20. "sort"
  21. "strings"
  22. "text/template"
  23. "time"
  24. "github.com/88250/lute/ast"
  25. "github.com/siyuan-note/logging"
  26. "github.com/siyuan-note/siyuan/kernel/av"
  27. "github.com/siyuan-note/siyuan/kernel/cache"
  28. "github.com/siyuan-note/siyuan/kernel/treenode"
  29. "github.com/siyuan-note/siyuan/kernel/util"
  30. )
  31. func RenderAttributeViewTable(attrView *av.AttributeView, view *av.View, query string,
  32. GetBlockAttrsWithoutWaitWriting func(id string) (ret map[string]string)) (ret *av.Table) {
  33. if nil == GetBlockAttrsWithoutWaitWriting {
  34. GetBlockAttrsWithoutWaitWriting = func(id string) (ret map[string]string) {
  35. ret = cache.GetBlockIAL(id)
  36. if nil == ret {
  37. ret = map[string]string{}
  38. }
  39. return
  40. }
  41. }
  42. ret = &av.Table{
  43. ID: view.ID,
  44. Icon: view.Icon,
  45. Name: view.Name,
  46. HideAttrViewName: view.HideAttrViewName,
  47. Columns: []*av.TableColumn{},
  48. Rows: []*av.TableRow{},
  49. Filters: view.Table.Filters,
  50. Sorts: view.Table.Sorts,
  51. }
  52. // 组装列
  53. for _, col := range view.Table.Columns {
  54. key, getErr := attrView.GetKey(col.ID)
  55. if nil != getErr {
  56. // 找不到字段则在视图中删除
  57. switch view.LayoutType {
  58. case av.LayoutTypeTable:
  59. for i, column := range view.Table.Columns {
  60. if column.ID == col.ID {
  61. view.Table.Columns = append(view.Table.Columns[:i], view.Table.Columns[i+1:]...)
  62. break
  63. }
  64. }
  65. }
  66. logging.LogWarnf("get key [%s] failed: %s", col.ID, getErr)
  67. av.SaveAttributeView(attrView)
  68. continue
  69. }
  70. ret.Columns = append(ret.Columns, &av.TableColumn{
  71. ID: key.ID,
  72. Name: key.Name,
  73. Type: key.Type,
  74. Icon: key.Icon,
  75. Options: key.Options,
  76. NumberFormat: key.NumberFormat,
  77. Template: key.Template,
  78. Relation: key.Relation,
  79. Rollup: key.Rollup,
  80. Date: key.Date,
  81. Wrap: col.Wrap,
  82. Hidden: col.Hidden,
  83. Width: col.Width,
  84. Pin: col.Pin,
  85. Calc: col.Calc,
  86. })
  87. }
  88. // 生成行
  89. rows := map[string][]*av.KeyValues{}
  90. for _, keyValues := range attrView.KeyValues {
  91. for _, val := range keyValues.Values {
  92. values := rows[val.BlockID]
  93. if nil == values {
  94. values = []*av.KeyValues{{Key: keyValues.Key, Values: []*av.Value{val}}}
  95. } else {
  96. values = append(values, &av.KeyValues{Key: keyValues.Key, Values: []*av.Value{val}})
  97. }
  98. rows[val.BlockID] = values
  99. }
  100. }
  101. // 过滤掉不存在的行
  102. var notFound []string
  103. for blockID, keyValues := range rows {
  104. blockValue := getRowBlockValue(keyValues)
  105. if nil == blockValue {
  106. notFound = append(notFound, blockID)
  107. continue
  108. }
  109. if blockValue.IsDetached {
  110. continue
  111. }
  112. if nil != blockValue.Block && "" == blockValue.Block.ID {
  113. notFound = append(notFound, blockID)
  114. continue
  115. }
  116. if nil == treenode.GetBlockTree(blockID) {
  117. notFound = append(notFound, blockID)
  118. }
  119. }
  120. for _, blockID := range notFound {
  121. delete(rows, blockID)
  122. }
  123. // 生成行单元格
  124. for rowID, row := range rows {
  125. var tableRow av.TableRow
  126. for _, col := range ret.Columns {
  127. var tableCell *av.TableCell
  128. for _, keyValues := range row {
  129. if keyValues.Key.ID == col.ID {
  130. tableCell = &av.TableCell{
  131. ID: keyValues.Values[0].ID,
  132. Value: keyValues.Values[0],
  133. ValueType: col.Type,
  134. }
  135. break
  136. }
  137. }
  138. if nil == tableCell {
  139. tableCell = &av.TableCell{
  140. ID: ast.NewNodeID(),
  141. ValueType: col.Type,
  142. }
  143. }
  144. tableRow.ID = rowID
  145. switch tableCell.ValueType {
  146. case av.KeyTypeNumber: // 格式化数字
  147. if nil != tableCell.Value && nil != tableCell.Value.Number && tableCell.Value.Number.IsNotEmpty {
  148. tableCell.Value.Number.Format = col.NumberFormat
  149. tableCell.Value.Number.FormatNumber()
  150. }
  151. case av.KeyTypeTemplate: // 渲染模板列
  152. tableCell.Value = &av.Value{ID: tableCell.ID, KeyID: col.ID, BlockID: rowID, Type: av.KeyTypeTemplate, Template: &av.ValueTemplate{Content: col.Template}}
  153. case av.KeyTypeCreated: // 填充创建时间列值,后面再渲染
  154. tableCell.Value = &av.Value{ID: tableCell.ID, KeyID: col.ID, BlockID: rowID, Type: av.KeyTypeCreated}
  155. case av.KeyTypeUpdated: // 填充更新时间列值,后面再渲染
  156. tableCell.Value = &av.Value{ID: tableCell.ID, KeyID: col.ID, BlockID: rowID, Type: av.KeyTypeUpdated}
  157. case av.KeyTypeRelation: // 清空关联列值,后面再渲染 https://ld246.com/article/1703831044435
  158. if nil != tableCell.Value && nil != tableCell.Value.Relation {
  159. tableCell.Value.Relation.Contents = nil
  160. }
  161. }
  162. FillAttributeViewTableCellNilValue(tableCell, rowID, col.ID)
  163. tableRow.Cells = append(tableRow.Cells, tableCell)
  164. }
  165. ret.Rows = append(ret.Rows, &tableRow)
  166. }
  167. // 渲染自动生成的列值,比如关联列、汇总列、创建时间列和更新时间列
  168. for _, row := range ret.Rows {
  169. for _, cell := range row.Cells {
  170. switch cell.ValueType {
  171. case av.KeyTypeRollup: // 渲染汇总列
  172. rollupKey, _ := attrView.GetKey(cell.Value.KeyID)
  173. if nil == rollupKey || nil == rollupKey.Rollup {
  174. break
  175. }
  176. relKey, _ := attrView.GetKey(rollupKey.Rollup.RelationKeyID)
  177. if nil == relKey || nil == relKey.Relation {
  178. break
  179. }
  180. relVal := attrView.GetValue(relKey.ID, row.ID)
  181. if nil == relVal || nil == relVal.Relation {
  182. break
  183. }
  184. destAv, _ := av.ParseAttributeView(relKey.Relation.AvID)
  185. if nil == destAv {
  186. break
  187. }
  188. destKey, _ := destAv.GetKey(rollupKey.Rollup.KeyID)
  189. if nil == destKey {
  190. continue
  191. }
  192. for _, blockID := range relVal.Relation.BlockIDs {
  193. destVal := destAv.GetValue(rollupKey.Rollup.KeyID, blockID)
  194. if nil == destVal {
  195. if destAv.ExistBlock(blockID) { // 数据库中存在行但是列值不存在是数据未初始化,这里补一个默认值
  196. destVal = av.GetAttributeViewDefaultValue(ast.NewNodeID(), rollupKey.Rollup.KeyID, blockID, destKey.Type)
  197. }
  198. if nil == destVal {
  199. continue
  200. }
  201. }
  202. if av.KeyTypeNumber == destKey.Type {
  203. destVal.Number.Format = destKey.NumberFormat
  204. destVal.Number.FormatNumber()
  205. }
  206. cell.Value.Rollup.Contents = append(cell.Value.Rollup.Contents, destVal.Clone())
  207. }
  208. cell.Value.Rollup.RenderContents(rollupKey.Rollup.Calc, destKey)
  209. // 将汇总列的值保存到 rows 中,后续渲染模板列的时候会用到,下同
  210. // Database table view template columns support reading relation, rollup, created and updated columns https://github.com/siyuan-note/siyuan/issues/10442
  211. keyValues := rows[row.ID]
  212. keyValues = append(keyValues, &av.KeyValues{Key: rollupKey, Values: []*av.Value{{ID: cell.Value.ID, KeyID: rollupKey.ID, BlockID: row.ID, Type: av.KeyTypeRollup, Rollup: cell.Value.Rollup}}})
  213. rows[row.ID] = keyValues
  214. case av.KeyTypeRelation: // 渲染关联列
  215. relKey, _ := attrView.GetKey(cell.Value.KeyID)
  216. if nil != relKey && nil != relKey.Relation {
  217. destAv, _ := av.ParseAttributeView(relKey.Relation.AvID)
  218. if nil != destAv {
  219. blocks := map[string]*av.Value{}
  220. for _, blockValue := range destAv.GetBlockKeyValues().Values {
  221. blocks[blockValue.BlockID] = blockValue
  222. }
  223. for _, blockID := range cell.Value.Relation.BlockIDs {
  224. if val := blocks[blockID]; nil != val {
  225. cell.Value.Relation.Contents = append(cell.Value.Relation.Contents, val)
  226. }
  227. }
  228. }
  229. }
  230. keyValues := rows[row.ID]
  231. keyValues = append(keyValues, &av.KeyValues{Key: relKey, Values: []*av.Value{{ID: cell.Value.ID, KeyID: relKey.ID, BlockID: row.ID, Type: av.KeyTypeRelation, Relation: cell.Value.Relation}}})
  232. rows[row.ID] = keyValues
  233. case av.KeyTypeCreated: // 渲染创建时间
  234. createdStr := row.ID[:len("20060102150405")]
  235. created, parseErr := time.ParseInLocation("20060102150405", createdStr, time.Local)
  236. if nil == parseErr {
  237. cell.Value.Created = av.NewFormattedValueCreated(created.UnixMilli(), 0, av.CreatedFormatNone)
  238. cell.Value.Created.IsNotEmpty = true
  239. } else {
  240. cell.Value.Created = av.NewFormattedValueCreated(time.Now().UnixMilli(), 0, av.CreatedFormatNone)
  241. }
  242. keyValues := rows[row.ID]
  243. createdKey, _ := attrView.GetKey(cell.Value.KeyID)
  244. keyValues = append(keyValues, &av.KeyValues{Key: createdKey, Values: []*av.Value{{ID: cell.Value.ID, KeyID: createdKey.ID, BlockID: row.ID, Type: av.KeyTypeCreated, Created: cell.Value.Created}}})
  245. rows[row.ID] = keyValues
  246. case av.KeyTypeUpdated: // 渲染更新时间
  247. ial := map[string]string{}
  248. block := row.GetBlockValue()
  249. if nil != block && !block.IsDetached {
  250. ial = GetBlockAttrsWithoutWaitWriting(row.ID)
  251. }
  252. updatedStr := ial["updated"]
  253. if "" == updatedStr && nil != block {
  254. cell.Value.Updated = av.NewFormattedValueUpdated(block.Block.Updated, 0, av.UpdatedFormatNone)
  255. cell.Value.Updated.IsNotEmpty = true
  256. } else {
  257. updated, parseErr := time.ParseInLocation("20060102150405", updatedStr, time.Local)
  258. if nil == parseErr {
  259. cell.Value.Updated = av.NewFormattedValueUpdated(updated.UnixMilli(), 0, av.UpdatedFormatNone)
  260. cell.Value.Updated.IsNotEmpty = true
  261. } else {
  262. cell.Value.Updated = av.NewFormattedValueUpdated(time.Now().UnixMilli(), 0, av.UpdatedFormatNone)
  263. }
  264. }
  265. keyValues := rows[row.ID]
  266. updatedKey, _ := attrView.GetKey(cell.Value.KeyID)
  267. keyValues = append(keyValues, &av.KeyValues{Key: updatedKey, Values: []*av.Value{{ID: cell.Value.ID, KeyID: updatedKey.ID, BlockID: row.ID, Type: av.KeyTypeUpdated, Updated: cell.Value.Updated}}})
  268. rows[row.ID] = keyValues
  269. }
  270. }
  271. }
  272. // 最后单独渲染模板列,这样模板列就可以使用汇总、关联、创建时间和更新时间列的值了
  273. // Database table view template columns support reading relation, rollup, created and updated columns https://github.com/siyuan-note/siyuan/issues/10442
  274. var renderTemplateErr error
  275. for _, row := range ret.Rows {
  276. for _, cell := range row.Cells {
  277. switch cell.ValueType {
  278. case av.KeyTypeTemplate: // 渲染模板列
  279. keyValues := rows[row.ID]
  280. ial := map[string]string{}
  281. block := row.GetBlockValue()
  282. if nil != block && !block.IsDetached {
  283. ial = GetBlockAttrsWithoutWaitWriting(row.ID)
  284. }
  285. content, renderErr := RenderTemplateCol(ial, keyValues, cell.Value.Template.Content)
  286. cell.Value.Template.Content = content
  287. if nil != renderErr {
  288. key, _ := attrView.GetKey(cell.Value.KeyID)
  289. keyName := ""
  290. if nil != key {
  291. keyName = key.Name
  292. }
  293. renderTemplateErr = fmt.Errorf("database [%s] template field [%s] rendering failed: %s", getAttrViewName(attrView), keyName, renderErr)
  294. }
  295. }
  296. }
  297. }
  298. if nil != renderTemplateErr {
  299. util.PushErrMsg(fmt.Sprintf(util.Langs[util.Lang][44], util.EscapeHTML(renderTemplateErr.Error())), 30000)
  300. }
  301. // 根据搜索条件过滤
  302. query = strings.TrimSpace(query)
  303. if "" != query {
  304. // 将连续空格转换为一个空格
  305. query = strings.Join(strings.Fields(query), " ")
  306. // 按空格分割关键字
  307. keywords := strings.Split(query, " ")
  308. // 使用 AND 逻辑 https://github.com/siyuan-note/siyuan/issues/11535
  309. var hitRows []*av.TableRow
  310. for _, row := range ret.Rows {
  311. hit := false
  312. for _, cell := range row.Cells {
  313. allKeywordsHit := true
  314. for _, keyword := range keywords {
  315. if !strings.Contains(strings.ToLower(cell.Value.String(true)), strings.ToLower(keyword)) {
  316. allKeywordsHit = false
  317. break
  318. }
  319. }
  320. if allKeywordsHit {
  321. hit = true
  322. break
  323. }
  324. }
  325. if hit {
  326. hitRows = append(hitRows, row)
  327. }
  328. }
  329. ret.Rows = hitRows
  330. if 1 > len(ret.Rows) {
  331. ret.Rows = []*av.TableRow{}
  332. }
  333. }
  334. // 自定义排序
  335. sortRowIDs := map[string]int{}
  336. if 0 < len(view.Table.RowIDs) {
  337. for i, rowID := range view.Table.RowIDs {
  338. sortRowIDs[rowID] = i
  339. }
  340. }
  341. sort.Slice(ret.Rows, func(i, j int) bool {
  342. iv := sortRowIDs[ret.Rows[i].ID]
  343. jv := sortRowIDs[ret.Rows[j].ID]
  344. if iv == jv {
  345. return ret.Rows[i].ID < ret.Rows[j].ID
  346. }
  347. return iv < jv
  348. })
  349. return
  350. }
  351. func RenderTemplateCol(ial map[string]string, rowValues []*av.KeyValues, tplContent string) (ret string, err error) {
  352. if "" == ial["id"] {
  353. block := getRowBlockValue(rowValues)
  354. if nil != block && nil != block.Block {
  355. ial["id"] = block.Block.ID
  356. }
  357. }
  358. if "" == ial["updated"] {
  359. block := getRowBlockValue(rowValues)
  360. if nil != block && nil != block.Block {
  361. ial["updated"] = time.UnixMilli(block.Block.Updated).Format("20060102150405")
  362. }
  363. }
  364. goTpl := template.New("").Delims(".action{", "}")
  365. tplFuncMap := util.BuiltInTemplateFuncs()
  366. SQLTemplateFuncs(&tplFuncMap)
  367. goTpl = goTpl.Funcs(tplFuncMap)
  368. tpl, err := goTpl.Parse(tplContent)
  369. if nil != err {
  370. logging.LogWarnf("parse template [%s] failed: %s", tplContent, err)
  371. return
  372. }
  373. buf := &bytes.Buffer{}
  374. dataModel := map[string]interface{}{} // 复制一份 IAL 以避免修改原始数据
  375. for k, v := range ial {
  376. dataModel[k] = v
  377. // Database template column supports `created` and `updated` built-in variables https://github.com/siyuan-note/siyuan/issues/9364
  378. createdStr := ial["id"]
  379. if "" != createdStr {
  380. createdStr = createdStr[:len("20060102150405")]
  381. }
  382. created, parseErr := time.ParseInLocation("20060102150405", createdStr, time.Local)
  383. if nil == parseErr {
  384. dataModel["created"] = created
  385. } else {
  386. logging.LogWarnf("parse created [%s] failed: %s", createdStr, parseErr)
  387. dataModel["created"] = time.Now()
  388. }
  389. updatedStr := ial["updated"]
  390. updated, parseErr := time.ParseInLocation("20060102150405", updatedStr, time.Local)
  391. if nil == parseErr {
  392. dataModel["updated"] = updated
  393. } else {
  394. dataModel["updated"] = time.Now()
  395. }
  396. }
  397. for _, rowValue := range rowValues {
  398. if 1 > len(rowValue.Values) {
  399. continue
  400. }
  401. v := rowValue.Values[0]
  402. if av.KeyTypeNumber == v.Type {
  403. if nil != v.Number && v.Number.IsNotEmpty {
  404. dataModel[rowValue.Key.Name] = v.Number.Content
  405. }
  406. } else if av.KeyTypeDate == v.Type {
  407. if nil != v.Date {
  408. if v.Date.IsNotEmpty {
  409. dataModel[rowValue.Key.Name] = time.UnixMilli(v.Date.Content)
  410. }
  411. if v.Date.IsNotEmpty2 {
  412. dataModel[rowValue.Key.Name+"_end"] = time.UnixMilli(v.Date.Content2)
  413. }
  414. }
  415. } else if av.KeyTypeRollup == v.Type {
  416. if 0 < len(v.Rollup.Contents) {
  417. var numbers []float64
  418. var contents []string
  419. for _, content := range v.Rollup.Contents {
  420. if av.KeyTypeNumber == content.Type {
  421. numbers = append(numbers, content.Number.Content)
  422. } else {
  423. contents = append(contents, content.String(true))
  424. }
  425. }
  426. if 0 < len(numbers) {
  427. dataModel[rowValue.Key.Name] = numbers
  428. } else {
  429. dataModel[rowValue.Key.Name] = contents
  430. }
  431. }
  432. } else if av.KeyTypeRelation == v.Type {
  433. if 0 < len(v.Relation.Contents) {
  434. var contents []string
  435. for _, content := range v.Relation.Contents {
  436. contents = append(contents, content.String(true))
  437. }
  438. dataModel[rowValue.Key.Name] = contents
  439. }
  440. } else {
  441. dataModel[rowValue.Key.Name] = v.String(true)
  442. }
  443. }
  444. if err = tpl.Execute(buf, dataModel); nil != err {
  445. logging.LogWarnf("execute template [%s] failed: %s", tplContent, err)
  446. return
  447. }
  448. ret = buf.String()
  449. return
  450. }
  451. func FillAttributeViewTableCellNilValue(tableCell *av.TableCell, rowID, colID string) {
  452. if nil == tableCell.Value {
  453. tableCell.Value = av.GetAttributeViewDefaultValue(tableCell.ID, colID, rowID, tableCell.ValueType)
  454. return
  455. }
  456. tableCell.Value.Type = tableCell.ValueType
  457. switch tableCell.ValueType {
  458. case av.KeyTypeText:
  459. if nil == tableCell.Value.Text {
  460. tableCell.Value.Text = &av.ValueText{}
  461. }
  462. case av.KeyTypeNumber:
  463. if nil == tableCell.Value.Number {
  464. tableCell.Value.Number = &av.ValueNumber{}
  465. }
  466. case av.KeyTypeDate:
  467. if nil == tableCell.Value.Date {
  468. tableCell.Value.Date = &av.ValueDate{}
  469. }
  470. case av.KeyTypeSelect:
  471. if 1 > len(tableCell.Value.MSelect) {
  472. tableCell.Value.MSelect = []*av.ValueSelect{}
  473. }
  474. case av.KeyTypeMSelect:
  475. if 1 > len(tableCell.Value.MSelect) {
  476. tableCell.Value.MSelect = []*av.ValueSelect{}
  477. }
  478. case av.KeyTypeURL:
  479. if nil == tableCell.Value.URL {
  480. tableCell.Value.URL = &av.ValueURL{}
  481. }
  482. case av.KeyTypeEmail:
  483. if nil == tableCell.Value.Email {
  484. tableCell.Value.Email = &av.ValueEmail{}
  485. }
  486. case av.KeyTypePhone:
  487. if nil == tableCell.Value.Phone {
  488. tableCell.Value.Phone = &av.ValuePhone{}
  489. }
  490. case av.KeyTypeMAsset:
  491. if 1 > len(tableCell.Value.MAsset) {
  492. tableCell.Value.MAsset = []*av.ValueAsset{}
  493. }
  494. case av.KeyTypeTemplate:
  495. if nil == tableCell.Value.Template {
  496. tableCell.Value.Template = &av.ValueTemplate{}
  497. }
  498. case av.KeyTypeCreated:
  499. if nil == tableCell.Value.Created {
  500. tableCell.Value.Created = &av.ValueCreated{}
  501. }
  502. case av.KeyTypeUpdated:
  503. if nil == tableCell.Value.Updated {
  504. tableCell.Value.Updated = &av.ValueUpdated{}
  505. }
  506. case av.KeyTypeCheckbox:
  507. if nil == tableCell.Value.Checkbox {
  508. tableCell.Value.Checkbox = &av.ValueCheckbox{}
  509. }
  510. case av.KeyTypeRelation:
  511. if nil == tableCell.Value.Relation {
  512. tableCell.Value.Relation = &av.ValueRelation{}
  513. }
  514. case av.KeyTypeRollup:
  515. if nil == tableCell.Value.Rollup {
  516. tableCell.Value.Rollup = &av.ValueRollup{}
  517. }
  518. }
  519. }
  520. func getAttributeViewContent(avID string,
  521. GetBlockAttrsWithoutWaitWriting func(id string) (ret map[string]string)) (content string) {
  522. if "" == avID {
  523. return
  524. }
  525. attrView, err := av.ParseAttributeView(avID)
  526. if nil != err {
  527. logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
  528. return
  529. }
  530. buf := bytes.Buffer{}
  531. buf.WriteString(attrView.Name)
  532. buf.WriteByte(' ')
  533. for _, v := range attrView.Views {
  534. buf.WriteString(v.Name)
  535. buf.WriteByte(' ')
  536. }
  537. if 1 > len(attrView.Views) {
  538. content = strings.TrimSpace(buf.String())
  539. return
  540. }
  541. var view *av.View
  542. for _, v := range attrView.Views {
  543. if av.LayoutTypeTable == v.LayoutType {
  544. view = v
  545. break
  546. }
  547. }
  548. if nil == view {
  549. content = strings.TrimSpace(buf.String())
  550. return
  551. }
  552. table := RenderAttributeViewTable(attrView, view, "", GetBlockAttrsWithoutWaitWriting)
  553. for _, col := range table.Columns {
  554. buf.WriteString(col.Name)
  555. buf.WriteByte(' ')
  556. }
  557. for _, row := range table.Rows {
  558. for _, cell := range row.Cells {
  559. if nil == cell.Value {
  560. continue
  561. }
  562. buf.WriteString(cell.Value.String(true))
  563. buf.WriteByte(' ')
  564. }
  565. }
  566. content = strings.TrimSpace(buf.String())
  567. return
  568. }
  569. func getRowBlockValue(keyValues []*av.KeyValues) (ret *av.Value) {
  570. for _, kv := range keyValues {
  571. if av.KeyTypeBlock == kv.Key.Type && 0 < len(kv.Values) {
  572. ret = kv.Values[0]
  573. break
  574. }
  575. }
  576. return
  577. }
  578. func getAttrViewName(attrView *av.AttributeView) string {
  579. ret := strings.TrimSpace(attrView.Name)
  580. if "" == ret {
  581. ret = util.Langs[util.Lang][105]
  582. }
  583. return ret
  584. }