The Master of vsoDoc can integrate Masters with the same name at drop time by using the MatchByName method.
However, it may not work well in some cases, and we are investigating the cause.
Which property does MatchByName refer to to integrate Master? Is it Master.Name? Or is it Master.NameU?
If you divide the case
The copy destination is Master (vsoDoc.Masters) of the visio file (A.vsdx).
There can be two cases for the copy source.
One is to copy the Shapes of the visio file (B.vsdx)
Or drop it from the vssx file (X.vssx) to A.vsdx.
When copying Shapes, do you refer to the Name or NameU of each Master?
Since the ReplaceShame method changes the name of Master,
I am dealing with the new Master and the old Master by swapping the names with the following procedure.
First, in preparation for ReplaceShape, change the MatchByName of all vsoMasters to False.
Then "Replace Shape" all vsoShape
For Each vsoMaster in vsoMasters
vsoMaster.MatchByName = False
Next
Remove the replaced Master from Masters
For Each vsoPage In vsoDoc.Pages
For Each vsoShape in vsoPage.Shapes
If Not (vsoShape.Master is Nothing) then
vsoShape.Master.MatchByName = False
Set vsoShape = vsoShape.ReplaceShape(vssxMasters.ItemU(vsoShape.Master.NameU), 1)
end if
Next
Next
Remove the replaced Master from Masters
For Each vsoMaster in vsoMasters
If InStr(vsoMaster.Name, ".") == 0 then
vsoMasters.ItemU(vsoMaster.NameU).Delete
end if
Next
Change MatchByName of all vsoMasters to True
For Each vsoMaster In vsoMasters
vsoMaster.MatchByName = True
Next
For Each vsoPage In vsoDoc.Pages
For Each vsoShape in vsoPage.Shapes
vsoShape.Master.MatchByName = True
Next
Next
vsoDoc.SaveAs NewName
############################################################
I examined the phenomenon related to MatchByName in three cases.
MatchByName = True in all of the following cases.
Case A-1
When using Master (NameU: "test") of the same stencil file (M.vssx)
Procedure
Place the "test" stencil in the A.vsdx file.
Copy the A.vsdx file and create the A-1_copy.vsdx file
Copy the "test" stencil in which A_copy.vsdx, and pasting it into the A.vsdx file.
Result
As expected, MatchByName works fine and the document stencil Masters doesn't increase.
Case B
Create a Master (NameU: "test") in the stencil file (M-1.vssx)
Create another Master (NameU: "test") in another stencil file (M-2.vssx)
In this case B, the Master Name U is same, but the Base ID and Unique ID are different. I think this is an important point.
Case B-1
Procedure
Place the "test" stencil of the M-1.vssx in the B.vsdx file.
Place the "test" stencil of the M-2.vssx in the C.vsdx file.
Copy the "test" stencil from the C.vsdx file and paste it into the B.vsdx file.
MatchByName doesn't work. "test.10" is added to the document stencil.
I suspect that this is because the stencil's BaseID and UniqueID are different.
Case B-2
Procedure
Place the M-1.vssx "test" stencil in the B.vsdx file.
Launch another VISIO application and drop the "test" stencil of M-2.vssx into the B.vsdx file.
Result
Unlike the B-1 result, MatchByName WORKS.
I can not understand the cause.
The following code is the code to rewrite the XML file in the vsdx file. By rewriting the Unique ID of the Masters of the vsdx file and the vssx file, MatchByName can be operated as expected.
import zipfile
from pathlib import Path
import xml.etree.ElementTree as ET
import sys
import shutil
import os
# https://yuukou-exp.plus/handle-xlsx-with-python-intro/
# https://livingdead0812.hatenablog.com/entry/2019/07/18/183322
# https://pg-chain.com/python-xml-read-write
# https://note.com/you_memolog/n/ne7d9e8d8a0d3
# https://stackoverflow.com/questions/16721010/write-a-xssfworkbook-to-a-zip-file
#visiopath = Path(sys.argv[1])
visiopath = Path(r"Flowchart_v1.0.17.vssx")
folderpath = visiopath.parent
xml_path = None
with zipfile.ZipFile(visiopath) as zf:
for info in zf.infolist():
if "masters.xml" in info.filename:
xml_path = info.filename
archive = zipfile.ZipFile(visiopath)
#archive = zipfile.ZipFile(visiopath, "w")
print(f"xml_path {xml_path}")
NameU_UniqueID_dict = {'OPERATOR' : '{00FBF49C-0014-0000-8E40-00608CF305B2}',
'TERMINATOR' : '{00634DFF-0015-0000-8E40-00608CF305B2}',
'BRANCH' : '{07639B38-0034-0000-8E40-00608CF305B2}',
'SQUARE' : '{07622477-0012-0000-8E40-00608CF305B2}',
'STARTER' : '{00489A6D-0032-0000-8E40-00608CF305B2}',
'CONNECTOR' : '{006BE21A-0004-0000-8E40-00608CF305B2}',
}
if xml_path is not None:
tree = ET.parse(archive.open(xml_path))
root = tree.getroot()
for child in root:
if child.attrib['NameU'] in NameU_UniqueID_dict.keys():
UniqueID = NameU_UniqueID_dict[child.attrib['NameU']]
child.attrib['UniqueID'] = UniqueID
tree.write('masters.xml')
print('finished writing.')
with zipfile.ZipFile(visiopath, 'a') as zf:
zf.write('masters.xml', arcname='visio\masters\masters.xml')
del archive
zippath = visiopath.with_suffix('.zip')
print(zippath)
print(visiopath)
shutil.move(visiopath, zippath)
shutil.unpack_archive(zippath, 'dir_out')
os.remove(zippath)
shutil.make_archive(zippath.parent / zippath.stem, format='zip', root_dir='dir_out')
#shutil.make_archive(zippath, format='zip', root_dir='dir_out')
shutil.move(zippath, visiopath)
When copying Shapes, do you refer to the Name or NameU of each Master?
NameU
When working with Visio objects, developers should always use universal names to reference their objects. This advice applies to more than names. A number of properties and methods in the Visio API have universal syntax equivalents. If you find a property and notice the same property exists but ending in a ‘U’, use the universal property. A classic example is to use the FormulaU property to get and set formulas instead of Formula.
Read more about Name and NameU
when you use ReplaceShape method in Document stecnil created new master, this new master have different Name and NameU anyway !
Related
By default Yocto adds build timestamp to the output image file name, but I would like to replace it by the revision of my integration Git repository (which references all my layers and configuration files). To achieve this, I put the following code to my image recipe:
def get_image_version(d):
import subprocess
import os.path
try:
parentRepo = os.path.dirname(d.getVar("COREBASE", True))
return subprocess.check_output(["git", "describe", "--tags", "--long", "--dirty"], cwd = parentRepo, stderr = subprocess.DEVNULL).strip().decode('UTF-8')
except:
return d.getVar("MACHINE", True) + "-" + d.getVar("DATETIME", True)
IMAGE_VERSION = "${#get_image_version(d)}"
IMAGE_NAME = "${IMAGE_BASENAME}-${IMAGE_VERSION}"
IMAGE_NAME[vardepsexclude] = "IMAGE_VERSION"
This code works properly until I change Git revision (e.g. by adding a new commit). Then I receive the following error:
ERROR: When reparsing /home/ubuntu/yocto/poky/../mylayer/recipes-custom/images/core-image-minimal.bb.do_image_tar, the basehash value changed from 63e1e69797d2813a4c36297517478a28 to 9788d4bf2950a23d0f758e4508b0a894. The metadata is not deterministic and this needs to be fixed.
I understand this happens because the image recipe has already been parsed with older Git revision, but why constant changes of the build timestamp do not cause the same error? How can I fix my code to overcome this problem?
The timestamp does not have this effect since its added to vardepsexclude:
https://docs.yoctoproject.org/bitbake/bitbake-user-manual/bitbake-user-manual-metadata.html#variable-flags
[vardepsexclude]: Specifies a space-separated list of variables that should be excluded from a variable’s dependencies for the purposes of calculating its signature.
You may need to add this in a couple of places, e.g.:
https://git.yoctoproject.org/poky/tree/meta/classes/image-artifact-names.bbclass#n7
IMAGE_VERSION_SUFFIX ?= "-${DATETIME}"
IMAGE_VERSION_SUFFIX[vardepsexclude] += "DATETIME SOURCE_DATE_EPOCH"
IMAGE_NAME ?= "${IMAGE_BASENAME}-${MACHINE}${IMAGE_VERSION_SUFFIX}"
After some research it turned out the problem was in this line
IMAGE_VERSION = "${#get_image_version(d)}"
because the function get_image_version() was called during parsing. I took inspiration from the source file in aehs29's post and moved the code to the anonymous Python function which is called after parsing.
I also had to add vardepsexclude attribute to the IMAGE_NAME variable. I tried to add vardepvalue flag to IMAGE_VERSION variable as well and in this particular case it did the same job as vardepsexclude. Mentioned Bitbake class uses both flags, but I think in my case using only one of them is enough.
The final code is below:
IMAGE_VERSION ?= "${MACHINE}-${DATETIME}"
IMAGE_NAME = "${IMAGE_BASENAME}-${IMAGE_VERSION}"
IMAGE_NAME[vardepsexclude] += "IMAGE_VERSION"
python () {
import subprocess
import os.path
try:
parentRepo = os.path.dirname(d.getVar("COREBASE", True))
version = subprocess.check_output(["git", "describe", "--tags", "--long", "--dirty"], cwd = parentRepo, stderr = subprocess.DEVNULL).strip().decode('UTF-8')
d.setVar("IMAGE_VERSION", version)
except:
bb.warning("Could not get Git revision, image will have default name.")
}
EDIT:
After some research I realized it's better to define a global variable in layer.conf file of the layer containing the recipes referencing the variable. The variable is set by a python script and is immediately expanded to prevent deterministic build warning:
layer.conf:
require conf/image-version.py.inc
IMAGE_VERSION := "${#get_image_version(d)}"
image-version.py.inc:
def get_image_version(d):
import subprocess
import os.path
try:
parentRepo = os.path.dirname(d.getVar("COREBASE", True))
return subprocess.check_output(["git", "describe", "--tags", "--long", "--dirty"], cwd = parentRepo, stderr = subprocess.DEVNULL).strip().decode('UTF-8')
except:
bb.warn("Could not determine image version. Default naming schema will be used.")
return d.getVar("MACHINE", True) + "-" + d.getVar("DATETIME", True)
I think this is cleaner approach which fits BitBake build system better.
Currently I have a configuration file like this:
project {
inputs {
baseFile {
paths = ["project/src/test/resources/inputs/parquet1/date=2020-11-01/"]
type = parquet
applyConversions = false
}
}
}
And I want to change the date "2020-11-01" to another one during run time. I read I need a new config object since it's immutable, I'm trying this but I'm not quite sure how to edit paths since it's a list and not a String and it definitely needs to be a list or else it's going to say I haven't configured a path for the parquet.
val newConfig = config.withValue("project.inputs.baseFile.paths"(0),
ConfigValueFactory.fromAnyRef("project/src/test/resources/inputs/parquet1/date=2020-10-01/"))
But I'm getting a:
Error com.typesafe.config.ConfigException$BadPath: path parameter: Invalid path 'project.inputs.baseFile.': path has a leading, trailing, or two adjacent period '.' (use quoted "" empty string if you want an empty element)
What's the correct way to set the new path?
One option you have, is to override the entire array:
import scala.collection.JavaConverters._
val mergedConfig = config.withValue("project.inputs.baseFile.paths",
ConfigValueFactory.fromAnyRef(Seq("project/src/test/resources/inputs/parquet1/date=2020-10-01/").asJava))
But a more elegant way to do this (IMHO), is to create a new config, and to use the existing as a fallback.
For example, we can create a new config:
val newJsonString = """project {
|inputs {
|baseFile {
| paths = ["project/src/test/resources/inputs/parquet1/date=2020-10-01/"]
|}}}""".stripMargin
val newConfig = ConfigFactory.parseString(newJsonString)
And now to merge them:
val mergedConfig = newConfig.withFallback(config)
The output of:
println(mergedConfig.getList("project.inputs.baseFile.paths"))
println(mergedConfig.getString("project.inputs.baseFile.type"))
is:
SimpleConfigList(["project/src/test/resources/inputs/parquet1/date=2020-10-01/"])
parquet
As expected.
You can read more about Merging config trees. Code run at Scastie.
I didn't find any way to replace one element of the array with withValue.
I am working on an educational script in swift. The aim is to facilitate collaboration between peers in a university level class on statistics. Each student is required to make suggestions (anonymously) to one of their colleagues' work. The script I have been writing works so that all markdown files in the directory are anonymized (ie, author and email yaml keys are removed) and new files with the contents are created and renamed with an email address.
So far I was able to remove author: and email: yaml keys from each file and I was also able to rename the files in the directory. However, I am struggling to create a way of randomizing files and emails. The point is that the original file should be passed on to another student, but without the last one knowing the author. The receiving student should be decided randomly. This randomization is what I am struggling with.
The MWE
It is a linux project and the easiest way I found to create a MWE was to write the script using the excellent John Sundell's marathon script structure. Below is the code:
import Yaml // marathon: https://github.com/behrang/YamlSwift.git
import Files // marathon:https://github.com/JohnSundell/Files.git
import Foundation
var email: String = ""
var document: String = ""
for file in Folder.current.files {
guard file.extension == "md" else {
continue
}
let content = try file.readAsString()
let pattern = "(?s)(?<=---).*(?=---)"
if let range = content.range(of: pattern, options: .regularExpression) {
let text = String(content[range])
let value = try! Yaml.load(text)
email = value["email"].string!
let author = value["author"].string!
let emailline = "email: " + email
let authorline = "author: " + author
document = content.replacingOccurrences(of: "\n\(emailline)", with: "")
document = content.replacingOccurrences(of: "\n\(authorline)", with: "")
}
// Creating new file with name from email and copying contents into it
//let folder = try Folder()
let file = try Folder.current.createFile(named: email)
try file.write(string: document)
}
An example md file:
---
# Metadata
title: hum
author: jecatatu
email: email#gmail.com
---
This is more text outside the yaml block
email: zwe#gmail.com
A second file:
# Metadata
title: My essay
author: brauser
email: brauser#gmail.com
---
Extra text
A third file:
# Metadata
title: My third essay
author: bestuser
email: bestuser#gmail.com
---
Extra text
Question
To start off, I don't need code. But code is welcome. Note that one can run the above example (provided you have marathon installed) with:
marathon run lf-araujo/collab
I guess I can solve this problem by iterating over the files in the directory in a random order. How can do that? My first thought was to create a dict with emails and filenames, and scramble these two.
What you need is a deterministic way of assigning one reviewer to one peer. You can achieve that by creating a two maps of (random -> article). Sort them by the random key and match the articles by index. An example in pseudo code:
/* Note that you may want to be sure that random keys are unique */
map reviewers = [random_integer(): article_one,
random_integer(): article_two,
...]
map articles = [random_integer(): article_one,
random_integer(): article_two,
...]
sort reviewers by key
sort articles by key
for (index in 0..length(reviewers)) {
assign_reviewer(author_from: reviewers[index], to: articles[index])
}
I have the below code which allows the user to drag and drop a folder to get the folder path. I then take this folder path and use it to pass through to a command line application in Windows using Popen. This all works fine except if there are spaces in the folder path, which then fails. I am currently getting round this by using win32api.GetShortPathName(folder_list) which shortens them to DOS 8.3 spec, but I would like to use the full absolute path. I know the command line application takes paths with spaces as I also use a batch file with drag and drop which works with spaces in paths. I have tried inserting escapes, etc, still no luck. How can I get this to work properly with full folder paths with spaces?
class SubmissionPane(wx.Panel):
def __init__(self, parent, queue_control):
wx.Panel.__init__(self, parent, -1)
self.parent = parent
self.queue_control = queue_control
self.selected_folder = None
self.txtTitle = wx.TextCtrl(self, pos=(125, 70), size=(215, 25), style= wx.SUNKEN_BORDER, value="Enter Series Title Here")
self.txtTitle.Show(False)
self.drop_target = MyFileDropTarget(self)
self.SetDropTarget(self.drop_target)
def SetSubmissionFolders(self, folder_list):
"""Called by the FileDropTarget when files are dropped"""
print "Setting submission folders", folder_list
self.tc_files.SetValue(','.join(folder_list))
self.selected_folders = folder_list
class MyFileDropTarget(wx.FileDropTarget):
""""""
def __init__(self, window):
wx.FileDropTarget.__init__(self)
print "Creating a drop file target..."
self.window = window
def OnDropFiles(self, x, y, filenames):
self.window.SetSubmissionFolders(filenames)
I then submit this to Popen like this:
command1 = commandLineApplication + folder_list
process = Popen(command1, shell=True, stdin=PIPE)
You probably just need to put quotes around each of the file paths. That usually works. The win32api.GetShortPathName is a neat trick though.
Here's one way to do it:
n = ['"%s"' % x for x in folderlist]
Then do the
','.join(folder_list)
You mentioned in your code.
I have looked around and have found a number of questions which approach using-a-string-to-define-the-class-name and dynamic-class-generation-in-coffeescript> but neither of them exactly address my problem, so I am wondering whether I making some fundamental mistake in my approach to the problem.
In the loop below I am looping through some data parsed from JSON. For each set of data I want to extend my class Robot with string = new Robot where string is a string.
Currently my code does not produce any errors, and successfully creates new Robots but since their name is a string, trying to access them with robot1.move() or robot2.doSomeOtherClassyThing() does not work and tells me they are undefined.
This seems like it should not require a verbose helper function to make it work. What am I missing here?
createRobots: -> # process robot commands
createXcoord = missionData.xCoord
createYcoord = missionData.yCoord
createOrient = missionData.orientation
createInstru = missionData.robotInstructions
for command in createOrient
robot = 'robot' + (_i + 1)
name = robot
robot = new Robot \ # create named Robot
name
, createXcoord[_i]
, createYcoord[_i]
, createOrient[_i]
, createInstru[_i]
console.log(robot)
I think what is happening is that the variable "robot = 'string' is written over when the robot = new Robot is declared.
The outcome I am hoping for is string1 = new Robot, "string2 = new Robot". Does that make sense? jsfiddle.net/7EN5y/1
You need to add them to a context. If you want them to be global, create a variable like this:
# Either the browser root, or the CommonJS (e.g. Node) module root
root = window or exports
If you want an object that holds robots, add such an object to the root.
root.robots = []
Then when creating robots, add them to such an object.
robot = 'robot' + (_i + 1)
name = robot
robot = new Robot # ...
root[name] = robot # or robots[name] = robot
You may then use code like robot1.move(), or robots.robot1.move() (depending on if you attached them to the root or not).