| // Copyright (C) 2016 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package parser |
| |
| import ( |
| "fmt" |
| "path/filepath" |
| "strings" |
| |
| "android.googlesource.com/platform/tools/gpu/framework/stringtable" |
| "android.googlesource.com/platform/tools/gpu/framework/stringtable/minidown" |
| "android.googlesource.com/platform/tools/gpu/framework/stringtable/minidown/node" |
| ) |
| |
| // Parse parses the given string table file. |
| func Parse(filename, data string) (*stringtable.StringTable, []error) { |
| var errs []error |
| |
| // Start by parsing the minidown |
| minidown, parseErrs := minidown.Parse(filename, data) |
| if len(parseErrs) > 0 { |
| errs = append(errs, parseErrs) |
| } |
| |
| // Process the minidown into stringtable entries. |
| entries, processErrs := process(minidown) |
| if len(processErrs) > 0 { |
| errs = append(errs, processErrs...) |
| } |
| |
| table := &stringtable.StringTable{ |
| Info: stringtable.Info{ |
| CultureCode: strings.Split(filepath.Base(filename), ".")[0], |
| }, |
| Entries: entries, |
| } |
| return table, errs |
| } |
| |
| func process(in node.Node) (map[string]stringtable.Node, []error) { |
| var errs []error |
| table := map[string]stringtable.Node{} |
| |
| var current string // Name of the current entry. |
| newlines := 0 // Number of pending newlines. |
| |
| add := func(n node.Node) { |
| if len(current) == 0 { |
| return // No entry assigned yet. Perhaps a file header message? |
| } |
| |
| // Convert the minidown node to a stringtable node. |
| converted, e := convert(n) |
| errs = append(errs, e...) |
| |
| // Check to see if this is a new entry, or appending to an existing. |
| existing := table[current] |
| if existing == nil { |
| // New entry. Simply assign to the table. |
| table[current] = converted |
| newlines = 0 |
| return |
| } |
| |
| block, ok := existing.(*stringtable.Block) |
| if !ok { |
| // Multiple nodes for entry. Box first entry into a block. |
| block = &stringtable.Block{Children: []stringtable.Node{existing}} |
| } |
| |
| // Add any pending line breaks |
| if newlines > 0 { |
| block.Children = append(block.Children, &stringtable.LineBreak{ |
| Lines: uint32(newlines), |
| }) |
| newlines = 0 |
| } |
| |
| // Add new node to the block. |
| block.Children = append(block.Children, converted) |
| |
| // Update table. |
| table[current] = block |
| } |
| |
| root, ok := in.(*node.Block) |
| if !ok { |
| return nil, []error{fmt.Errorf("Expected root to be a block, instead got %T", in)} |
| } |
| |
| for _, n := range root.Children { |
| switch n := n.(type) { |
| case *node.NewLine: |
| newlines++ |
| |
| case *node.Heading: |
| // Check for a new table entry. These are declared as H1 headings. |
| if n.Scale == 1 { |
| if text, ok := n.Body.(*node.Text); ok { |
| // Change current. |
| current = text.Text |
| |
| // Check for duplicates. |
| if _, dup := table[current]; dup { |
| errs = append(errs, fmt.Errorf("Duplicate stringtable entry '%s'", current)) |
| } |
| } else { |
| errs = append(errs, fmt.Errorf("Entry name must be simple text, instead got %T", n.Body)) |
| } |
| } else { |
| add(n) // A heading, but not H1. Add to existing entry. |
| } |
| |
| default: |
| add(n) |
| } |
| } |
| return table, errs |
| } |
| |
| func convert(in node.Node) (stringtable.Node, []error) { |
| switch in := in.(type) { |
| case *node.Text: |
| return &stringtable.Text{Text: in.Text}, nil |
| |
| case *node.Whitespace: |
| return &stringtable.Whitespace{}, nil |
| |
| case *node.Link: |
| body, errsA := convert(in.Body) |
| target, errsB := convert(in.Target) |
| return &stringtable.Link{Body: body, Target: target}, append(errsA, errsB...) |
| |
| case *node.Tag: |
| return &stringtable.Parameter{Key: in.Identifier}, nil |
| |
| default: |
| return nil, []error{fmt.Errorf("Stringtable does not currently support %T", in)} |
| } |
| } |