283 lines
10 KiB
Ruby
283 lines
10 KiB
Ruby
# Copyright (c) 2018(-2023) STMicroelectronics.
|
|
# All rights reserved.
|
|
#
|
|
# This file is part of the TouchGFX 4.21.2 distribution.
|
|
#
|
|
# This software is licensed under terms that can be found in the LICENSE file in
|
|
# the root directory of this software component.
|
|
# If no LICENSE file comes with this software, it is provided AS-IS.
|
|
#
|
|
###############################################################################/
|
|
# coding: iso-8859-1
|
|
require 'nokogiri'
|
|
require 'rubyxl'
|
|
require 'rubyXL/convenience_methods'
|
|
require 'lib/text_entries'
|
|
require 'lib/xml_reader'
|
|
require 'lib/xml_writer'
|
|
|
|
# From https://github.com/weshatheleopard/rubyXL/wiki/How-to
|
|
class RubyXL::Cell
|
|
def unlock
|
|
xf = get_cell_xf.dup
|
|
xf.protection = xf.protection&.dup || RubyXL::Protection.new
|
|
xf.protection.locked = false
|
|
xf.apply_protection = true
|
|
self.style_index = workbook.register_new_xf(xf)
|
|
end
|
|
end
|
|
class RubyXL::Worksheet
|
|
def add_frozen_split(row:, column:)
|
|
worksheetview = RubyXL::WorksheetView.new
|
|
worksheetview.pane = RubyXL::Pane.new(:top_left_cell => RubyXL::Reference.new(row,column),
|
|
:y_split => row,
|
|
:x_split => column,
|
|
:state => 'frozenSplit',
|
|
:activePane => 'bottomRight')
|
|
worksheetviews = RubyXL::WorksheetViews.new
|
|
worksheetviews << worksheetview
|
|
self.sheet_views = worksheetviews
|
|
end
|
|
end
|
|
|
|
class TranslationIO
|
|
def initialize(file_name, translation_name)
|
|
@xml_doc = XMLReader.new.read(file_name)
|
|
@translation_name = translation_name
|
|
@file_name = file_name
|
|
@xml_file_version = @xml_doc.at("TextDatabase")["Version"]
|
|
end
|
|
|
|
WHITE = 'FFFFFF'
|
|
BLACK = '000000'
|
|
|
|
HEADER_BACKGROUND = '4472C4'
|
|
HEADER_FOREGROUND = WHITE
|
|
FIRST_ROW_BACKGROUND = 'B4C6E7'
|
|
FIRST_ROW_FOREGROUND = BLACK
|
|
SECOND_ROW_BACKGROUND = 'D9E1F2'
|
|
SECOND_ROW_FOREGROUND = BLACK
|
|
|
|
SHEET_NAME = 'TouchGFX Translation'
|
|
TEXT_ID = 'Text Id'
|
|
|
|
def exportExcel(languages)
|
|
workbook = RubyXL::Workbook.new
|
|
worksheet = workbook[0]
|
|
worksheet.sheet_name = SHEET_NAME
|
|
|
|
existing_languages = get_languages
|
|
if languages.empty?
|
|
# Empty string means "put TextID in this column"
|
|
languages = [ '' ] + existing_languages
|
|
else
|
|
# Empty string means "put TextID in this column"
|
|
languages = [''] + languages.map do |lang|
|
|
lang_upcase = lang.upcase
|
|
actual_language = existing_languages.find { |l| l.upcase == lang_upcase }
|
|
fail "ERROR: Unknown export language: #{lang}" if !actual_language
|
|
actual_language
|
|
end
|
|
end
|
|
languages.uniq!
|
|
languages.each_with_index do |lang, column|
|
|
cell = worksheet.add_cell(0, column, lang.empty? ? TEXT_ID : lang)
|
|
cell.change_font_color(HEADER_FOREGROUND)
|
|
cell.change_fill(HEADER_BACKGROUND)
|
|
cell.change_font_bold(true)
|
|
worksheet.change_column_width(column, lang.empty? ? 20 : 30)
|
|
end
|
|
|
|
# This line is only needed if the font_size of each cell is to be updated (inside the loop below)
|
|
# typography_map = @typographies.inject({}) { |map,typo| map[typo.name] = typo; map }
|
|
get_text_entries.each_with_index do |text, row|
|
|
languages.each_with_index do |lang, column|
|
|
cell = worksheet.add_cell(row+1, column, lang.empty? ? text.text_id : text.translation_in(lang))
|
|
cell.change_vertical_alignment('top')
|
|
if (row % 2) == 0
|
|
cell.change_font_color(FIRST_ROW_FOREGROUND)
|
|
cell.change_fill(FIRST_ROW_BACKGROUND)
|
|
else
|
|
cell.change_font_color(SECOND_ROW_FOREGROUND)
|
|
cell.change_fill(SECOND_ROW_BACKGROUND)
|
|
end
|
|
cell.change_font_bold(lang.empty?)
|
|
cell.change_border(:bottom, 'thin')
|
|
if !lang.empty?
|
|
cell.change_border(:right, 'thin')
|
|
cell.change_text_wrap(true)
|
|
# Lines only needed if the font size of each cell is to be updated
|
|
#typography_name = text.typographies[lang] || text.typography
|
|
#cell.change_font_size((typography_map[typography_name].font_size / 1.5).to_i)
|
|
alignment = text.alignment_in(lang) || text.alignment
|
|
cell.change_horizontal_alignment(alignment.downcase)
|
|
cell.unlock
|
|
end
|
|
end
|
|
end
|
|
|
|
worksheet.add_frozen_split(:row => 1, :column => 1)
|
|
worksheet.sheet_protection = RubyXL::WorksheetProtection.new(sheet: true, format_columns: false, format_rows: false)
|
|
|
|
workbook.write(@translation_name)
|
|
end
|
|
|
|
def importExcel(languages)
|
|
require 'lib/xml_writer'
|
|
|
|
workbook = RubyXL::Parser.parse(@translation_name)
|
|
worksheet = workbook.worksheets.find { |sheet| sheet.sheet_name == SHEET_NAME }
|
|
fail "ERROR: \"#{@translation_name}\" does not contain a sheet called \"#{SHEET_NAME}\"" if !worksheet
|
|
|
|
existing_languages = get_languages
|
|
|
|
header = [] # Collect the header with correctly capitalized languages
|
|
text_id_column = nil # Which column contains the TEXT_ID
|
|
import_columns = [] # Which columns to import
|
|
column = 0
|
|
while column < worksheet[0].size
|
|
if worksheet[0][column]
|
|
lang_cell = worksheet[0][column].value
|
|
if !lang_cell.empty?
|
|
lang_upcase = lang_cell.upcase
|
|
if lang_upcase == TEXT_ID.upcase
|
|
text_id_column = column
|
|
fail "ERROR: Multiple columns contain \"#{TEXT_ID}\"" if header.include?('')
|
|
header << ''
|
|
else
|
|
# Find the language with the correct capitalization
|
|
orig_lang = existing_languages.find { |l| l.upcase == lang_upcase }
|
|
# Fail if all languages should be imported AND the language from the spreadsheet is illegal
|
|
fail "ERROR: Text Database does not contain language \"#{lang_cell}\", create the language in the TouchGFX Designer" if languages.empty? && !orig_lang
|
|
# if no languages specified, import all. Otherwise only import if language is wanted
|
|
if languages.empty? || languages.any? { |l| l.upcase == lang_upcase }
|
|
import_columns += [ column ]
|
|
end
|
|
fail "ERROR: Multiple columns contain translations for language \"#{orig_lang}\"" if header.include?(orig_lang)
|
|
header << orig_lang
|
|
end
|
|
end
|
|
end
|
|
column += 1
|
|
end
|
|
|
|
upper_languages = languages.map(&:upcase)
|
|
upper_existing_languages = existing_languages.map(&:upcase)
|
|
# Did we ask to import a language (on the command line) which does not exist in the spreadsheet?
|
|
fail "ERROR: Unknown language(s) #{(upper_languages - upper_existing_languages)*','}" if !(upper_languages - upper_existing_languages).empty?
|
|
fail "ERROR: Missing column \"#{TEXT_ID}\"" if !text_id_column
|
|
|
|
text_nodes = get_text_nodes_map
|
|
# Row 0 is the header
|
|
row = 1
|
|
all_text_ids = []
|
|
while row < worksheet.sheet_data.rows.size
|
|
if worksheet[row] && worksheet[row][text_id_column]
|
|
text_id = worksheet[row][text_id_column].value
|
|
if text_id && !text_id.empty?
|
|
fail "ERROR: Extra translations of Text Id \"#{text_id}\" given in line #{row}" if all_text_ids.include?(text_id)
|
|
import_columns.each do |column|
|
|
text_node = text_nodes[text_id]
|
|
fail "ERROR: The Text Id \"#{text_id}\" in line #{row} does not exist in the database" if !text_node
|
|
cell = worksheet[row][column]
|
|
cell_text = cell ? cell.value.to_s : ''
|
|
set_text_node_translation(text_node, header[column], cell_text)
|
|
#puts "Setting #{text_id}.#{header[column]} = #{worksheet[row][column].value}"
|
|
end
|
|
all_text_ids << text_id
|
|
end
|
|
end
|
|
row += 1
|
|
end
|
|
|
|
all_predefined_ids = get_text_ids
|
|
if !(all_predefined_ids - all_text_ids).empty?
|
|
puts "WARNING: \"#{@translation_name}\" does not contain the following Text Id's: #{(all_predefined_ids - all_text_ids)*', '}"
|
|
end
|
|
|
|
XMLWriter.new.write(@file_name, @xml_doc)
|
|
end
|
|
|
|
protected
|
|
def empty_to_nil(str)
|
|
str ? str.strip.empty? ? nil : str.strip : nil
|
|
end
|
|
|
|
#Get array of all Languages in XML
|
|
def get_languages
|
|
@xml_doc.xpath("/TextDatabase/Languages/Language").inject([]) do |languages, lang_node|
|
|
language = empty_to_nil(lang_node["Id"])
|
|
languages.push(language)
|
|
end
|
|
end
|
|
|
|
#Get array of all text IDs in XML
|
|
def get_text_ids
|
|
@xml_doc.xpath("/TextDatabase/Texts/TextGroup/Text").map{ |text_node| text_node["Id"] }
|
|
end
|
|
|
|
#Map of textId to XML node
|
|
def get_text_nodes_map
|
|
@xml_doc.xpath("/TextDatabase/Texts/TextGroup/Text").inject({}) do |nodes, text_node|
|
|
nodes[text_node["Id"]] = text_node
|
|
nodes
|
|
end
|
|
end
|
|
|
|
#Update the translation of a text
|
|
def set_text_node_translation(text_node, language, new_translation)
|
|
text_node.xpath("./Translation").each do |translation_node|
|
|
if translation_node["Language"] == language
|
|
#Remove old translation
|
|
translation_node.child.remove if translation_node.child
|
|
|
|
translation_node.add_child(Nokogiri::XML::Text.new(new_translation, @xml_doc))
|
|
end
|
|
end
|
|
end
|
|
|
|
#Class holding single text with ID, default alignment, translations, and alignments
|
|
class TextEntry
|
|
def initialize(text_id, alignment)
|
|
@text_id = text_id
|
|
@alignment = alignment
|
|
@alignments = {} # Language -> alignment
|
|
@translations = {} # Language -> text (translation)
|
|
end
|
|
def text_id
|
|
@text_id
|
|
end
|
|
def translation_in(language)
|
|
@translations[language]
|
|
end
|
|
def alignment
|
|
@alignment
|
|
end
|
|
def alignment_in(language)
|
|
@alignments[language] || alignment
|
|
end
|
|
def set_alignment(language, alignment)
|
|
@alignments[language] = alignment
|
|
end
|
|
def set_translation(language, text)
|
|
@translations[language] = text
|
|
end
|
|
end
|
|
|
|
#Compute array of TextEntry objects
|
|
def get_text_entries
|
|
texts = []
|
|
@xml_doc.xpath("/TextDatabase/Texts/TextGroup/Text").each do |text_node|
|
|
text = TextEntry.new(text_node["Id"], text_node["Alignment"])
|
|
text_node.search("Translation").each do |translation|
|
|
language = translation["Language"]
|
|
alignment = translation["Alignment"]
|
|
text.set_alignment(language, alignment) unless alignment.nil?
|
|
text.set_translation(language, translation.text)
|
|
end
|
|
texts << text
|
|
end
|
|
texts
|
|
end
|
|
end
|